// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QTest>
#include <qpainter.h>
#ifndef QT_NO_WIDGETS
#include <qdrawutil.h>
#include <qwidget.h>
#endif
#include <qguiapplication.h>
#include <qfontmetrics.h>
#include <qbitmap.h>
#include <qimage.h>
#include <qthread.h>
#include <limits.h>
#include <math.h>
#include <qpaintengine.h>
#include <qpixmap.h>
#include <qrandom.h>

#include <private/qdrawhelper_p.h>
#include <qpainter.h>
#include <qpainterpath.h>
#include <qqueue.h>
#include <qscreen.h>

#ifndef QT_NO_WIDGETS
#include <qgraphicsview.h>
#include <qgraphicsscene.h>
#include <qgraphicsproxywidget.h>
#endif
#include <qfontdatabase.h>

Q_DECLARE_METATYPE(QGradientStops)
Q_DECLARE_METATYPE(QPainterPath)
Q_DECLARE_METATYPE(QImage::Format)
Q_DECLARE_METATYPE(QPainter::CompositionMode)

class tst_QPainter : public QObject
{
Q_OBJECT

public:
    tst_QPainter();

    enum ClipType { ClipRect, ClipRectF, ClipRegionSingle, ClipRegionMulti, ClipPathR, ClipPath };
    Q_ENUM(ClipType);

private slots:
    void cleanupTestCase();
    void getSetCheck();
#ifndef QT_NO_WIDGETS
    void drawPixmap_comp_data();
    void drawPixmap_comp();
#endif
    void saveAndRestore_data();
    void saveAndRestore();

#ifndef QT_NO_WIDGETS
    void drawBorderPixmap();
#endif
    void drawPixmapFragments();
    void drawPixmapNegativeScale();
    void drawPixmapRounding();

    void drawLine_data();
    void drawLine();
    void drawLine_clipped();
    void drawLine_task121143();
    void drawLine_task216948();

    void drawLine_task190634();
    void drawLine_task229459();
    void drawLine_task234891();
    void drawLineEndPoints();

    void drawRect_data() { fillData(); }
    void drawRect();
    void drawRect2();

    void fillRect_data();
    void fillRect();
    void fillRect2_data();
    void fillRect2();
    void fillRect3_data() { fillRect2_data(); }
    void fillRect3();
    void fillRect4_data() { fillRect2_data(); }
    void fillRect4();
    void fillRectNonPremul_data();
    void fillRectNonPremul();

    void fillRectRGB30_data();
    void fillRectRGB30();

    void drawEllipse_data();
    void drawEllipse();
    void drawClippedEllipse_data();
    void drawClippedEllipse();

    void drawPath_data();
    void drawPath();
    void drawPath2();
    void drawPath3();

    void drawRoundedRect_data() { fillData(); }
    void drawRoundedRect();

    void qimageFormats_data();
    void qimageFormats();
    void textOnTransparentImage();

    void setWindow();

    void combinedTransform();
    void renderHints();

    void disableEnableClipping();
    void setClipRect();
    void clipRect();
    void setEqualClipRegionAndPath_data();
    void setEqualClipRegionAndPath();

    void clipRectSaveRestore();
    void clipStateSaveRestore();

    void clippedFillPath_data();
    void clippedFillPath();
    void clippedLines_data();
    void clippedLines();
    void clippedPolygon_data();
    void clippedPolygon();
    void clippedText();

    void clipBoundingRect();
    void transformedClip();
    void scaledClipConsistency_data();
    void scaledClipConsistency();

    void setOpacity_data();
    void setOpacity();

    void drawhelper_blend_untransformed_data();
    void drawhelper_blend_untransformed();
    void drawhelper_blend_tiled_untransformed_data();
    void drawhelper_blend_tiled_untransformed();

    void porterDuff_warning();

    void drawhelper_blend_color();

#ifndef QT_NO_WIDGETS
    void childWidgetViewport();
#endif

    void fillRect_objectBoundingModeGradient();
    void fillRect_stretchToDeviceMode();
    void monoImages();

    void linearGradientSymmetry_data();
    void linearGradientSymmetry();
    void gradientInterpolation();

    void gradientPixelFormat_data();
    void gradientPixelFormat();

#if QT_CONFIG(raster_64bit)
    void linearGradientRgb30_data();
    void linearGradientRgb30();
    void radialGradientRgb30_data();
    void radialGradientRgb30();
#endif

    void radialGradient_QTBUG120332_ubsan();
    void radialGradient_QTBUG130992_crash();
    void fpe_pixmapTransform();
    void fpe_zeroLengthLines();
    void fpe_divByZero();

    void fpe_steepSlopes_data();
    void fpe_steepSlopes();
    void fpe_rasterizeLine_task232012();

    void fpe_radialGradients();

    void rasterizer_asserts();
    void rasterizer_negativeCoords();

    void blendOverFlow_data();
    void blendOverFlow();

    void largeImagePainting_data();
    void largeImagePainting();

    void imageScaling_task206785();

    void outlineFillConsistency();

    void drawImage_task217400_data();
    void drawImage_task217400();
    void drawImage_1x1();
    void drawImage_task258776();
    void drawImage_QTBUG28324();
    void drawRect_task215378();
    void drawRect_task247505();

#if defined(Q_OS_MAC)
    void drawText_subPixelPositionsInRaster_qtbug5053();
#endif

    void drawImage_data();
    void drawImage();

    void clippedImage();

    void stateResetBetweenQPainters();

    void imageCoordinateLimit();
    void imageBlending_data();
    void imageBlending();
    void imageBlending_clipped();

    void paintOnNullPixmap();
    void checkCompositionMode();

    void drawPolygon();

    void inactivePainter();

    void extendedBlendModes();

    void zeroOpacity();
    void clippingBug();
    void emptyClip();

    void taskQT4444_dontOverflowDashOffset();

    void painterBegin();
    void setPenColorOnImage();
    void setPenColorOnPixmap();

#ifndef QT_NO_WIDGETS
    void QTBUG5939_attachPainterPrivate();
#endif

    void drawPointScaled();

    void QTBUG14614_gradientCacheRaceCondition();
    void drawTextOpacity();
    void drawPathOpacity();

    void QTBUG17053_zeroDashPattern();

    void QTBUG38781_NoBrushAndQBitmap();

    void drawTextOutsideGuiThread();

    void drawTextWithComplexBrush();
    void QTBUG26013_squareCapStroke();
    void QTBUG25153_drawLine();

    void cosmeticStrokerClipping_data();
    void cosmeticStrokerClipping();

    void blendARGBonRGB_data();
    void blendARGBonRGB();

    void RasterOp_NotDestination();
    void drawTextNoHinting();

    void drawPolyline_data();
    void drawPolyline();

    void QTBUG50153_drawImage_assert();

    void rotateImage_data();
    void rotateImage();

    void QTBUG56252();

    void blendNullRGB32();
    void toRGB64();

    void fillPolygon();

    void textOnArgb32();
    void drawImageAtPointF();
    void scaledDashes();
#if QT_CONFIG(raster_fp)
    void hdrColors();
#endif

    void alphaBlitToNonAlphaFormats_data();
    void alphaBlitToNonAlphaFormats();

    void floatRounding();

private:
    void fillData();
    void setPenColor(QPainter& p);
    QColor baseColor( int k, int intensity=255 );
    QImage getResImage( const QString &dir, const QString &addition, const QString &extension );
    QBitmap getBitmap( const QString &dir, const QString &filename, bool mask );
};

// Testing get/set functions
void tst_QPainter::getSetCheck()
{
    QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied);
    QPainter obj1;
    obj1.begin(&img);
    // CompositionMode QPainter::compositionMode()
    // void QPainter::setCompositionMode(CompositionMode)
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceOver));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceOver), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOver));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOver), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Clear));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Clear), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Source));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Source), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Destination));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Destination), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceIn));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceIn), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationIn));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationIn), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceOut));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceOut), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOut));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationOut), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_SourceAtop));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_SourceAtop), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_DestinationAtop));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_DestinationAtop), obj1.compositionMode());
    obj1.setCompositionMode(QPainter::CompositionMode(QPainter::CompositionMode_Xor));
    QCOMPARE(QPainter::CompositionMode(QPainter::CompositionMode_Xor), obj1.compositionMode());

    // const QPen & QPainter::pen()
    // void QPainter::setPen(const QPen &)
    QPen var3(Qt::red);
    obj1.setPen(var3);
    QCOMPARE(var3, obj1.pen());
    obj1.setPen(QPen());
    QCOMPARE(QPen(), obj1.pen());

    // const QBrush & QPainter::brush()
    // void QPainter::setBrush(const QBrush &)
    QBrush var4(Qt::red);
    obj1.setBrush(var4);
    QCOMPARE(var4, obj1.brush());
    obj1.setBrush(QBrush());
    QCOMPARE(QBrush(), obj1.brush());

    // const QBrush & QPainter::background()
    // void QPainter::setBackground(const QBrush &)
    QBrush var5(Qt::yellow);
    obj1.setBackground(var5);
    QCOMPARE(var5, obj1.background());
    obj1.setBackground(QBrush());
    QCOMPARE(QBrush(), obj1.background());

    // bool QPainter::matrixEnabled()
    // void QPainter::setMatrixEnabled(bool)
    obj1.setWorldMatrixEnabled(false);
    QCOMPARE(false, obj1.worldMatrixEnabled());
    obj1.setWorldMatrixEnabled(true);
    QCOMPARE(true, obj1.worldMatrixEnabled());

    // bool QPainter::viewTransformEnabled()
    // void QPainter::setViewTransformEnabled(bool)
    obj1.setViewTransformEnabled(false);
    QCOMPARE(false, obj1.viewTransformEnabled());
    obj1.setViewTransformEnabled(true);
    QCOMPARE(true, obj1.viewTransformEnabled());
}


tst_QPainter::tst_QPainter()
{
    // QtTestCase sets this to false, but this turns off alpha pixmaps on Unix.
    QGuiApplication::setDesktopSettingsAware(true);
}

void tst_QPainter::cleanupTestCase()
{
    QFile::remove(QLatin1String("dest.png"));
    QFile::remove(QLatin1String("expected.png"));
    QFile::remove(QLatin1String("foo.png"));
}

#ifndef QT_NO_WIDGETS
void tst_QPainter::drawPixmap_comp_data()
{
    if (QGuiApplication::primaryScreen()->depth() < 24)
        QSKIP("Test only works on 32 bit displays");

    QTest::addColumn<uint>("dest");
    QTest::addColumn<uint>("source");

    QTest::newRow("0% on 0%, 1")           << 0x00000000u<< 0x00000000u;
    QTest::newRow("0% on 0%, 2")           << 0x00007fffu << 0x00ff007fu;

    QTest::newRow("50% on a=0%")           << 0x00000000u << 0x7fff0000u;
    QTest::newRow("50% on a=50%")          << 0x7f000000u << 0x7fff0000u;
    QTest::newRow("50% on deadbeef")      << 0xdeafbeefu <<  0x7fff0000u;
    QTest::newRow("deadbeef on a=0%")      << 0x00000000u << 0xdeadbeefu;
    QTest::newRow("deadbeef on a=50%")     << 0x7f000000u << 0xdeadbeefu;
    QTest::newRow("50% blue on 50% red")   << 0x7fff0000u << 0x7f0000ffu;
    QTest::newRow("50% blue on 50% green") << 0x7f00ff00u << 0x7f0000ffu;
    QTest::newRow("50% red on 50% green")  << 0x7f00ff00u << 0x7fff0000u;
    QTest::newRow("0% on 50%")             << 0x7fff00ffu << 0x00ffffffu;
    QTest::newRow("100% on deadbeef")      << 0xdeafbeefu << 0xffabcdefu;
    QTest::newRow("100% on a=0%")           << 0x00000000u << 0xffabcdefu;
}

QRgb qt_compose_alpha(QRgb source, QRgb dest)
{
    int r1 = qRed(dest), g1 = qGreen(dest), b1 = qBlue(dest), a1 = qAlpha(dest);
    int r2 = qRed(source), g2 = qGreen(source), b2 = qBlue(source), a2 = qAlpha(source);

    int alpha = qMin(a2 + ((255 - a2) * a1 + 127) / 255, 255);
    if (alpha == 0)
        return qRgba(0, 0, 0, 0);

    return qRgba(
        qMin((r2 * a2 + (255 - a2) * r1 * a1 / 255) / alpha, 255),
        qMin((g2 * a2 + (255 - a2) * g1 * a1 / 255) / alpha, 255),
        qMin((b2 * a2 + (255 - a2) * b1 * a1 / 255) / alpha, 255),
        alpha);
}

/* Tests that drawing masked pixmaps works
*/
void tst_QPainter::drawPixmap_comp()
{
#ifdef Q_OS_MAC
    QSKIP("Mac has other ideas about alpha composition");
#endif
    QFETCH(uint, dest);
    QFETCH(uint, source);

    QRgb expected = qt_compose_alpha(source, dest);

    QColor c1(qRed(dest), qGreen(dest), qBlue(dest), qAlpha(dest));
    QColor c2(qRed(source), qGreen(source), qBlue(source), qAlpha(source));

    QPixmap destPm(10, 10), srcPm(10, 10);
    destPm.fill(c1);
    srcPm.fill(c2);

    QPainter p(&destPm);
    p.drawPixmap(0, 0, srcPm);
    p.end();

    QImage result = destPm.toImage().convertToFormat(QImage::Format_ARGB32);
    bool different = false;
    for (int y=0; y<result.height(); ++y)
        for (int x=0; x<result.width(); ++x) {
            bool diff;
            if (qAlpha(expected) == 0) {
                diff = qAlpha(result.pixel(x, y)) != 0;
            } else {
                // Compensate for possible roundoff / platform fudge
                int off = 1;
                QRgb pix = result.pixel(x, y);
                diff = (qAbs(qRed(pix) - qRed(expected)) > off)
                             || (qAbs(qGreen(pix) - qGreen(expected)) > off)
                             || (qAbs(qBlue(pix) - qBlue(expected)) > off)
                             || (qAbs(qAlpha(pix) - qAlpha(expected)) > off);
            }
            if (diff && !different)
                qDebug( "Different at %d,%d pixel [%d,%d,%d,%d] expected [%d,%d,%d,%d]", x, y,
                        qRed(result.pixel(x, y)), qGreen(result.pixel(x, y)),
                        qBlue(result.pixel(x, y)), qAlpha(result.pixel(x, y)),
                        qRed(expected), qGreen(expected), qBlue(expected), qAlpha(expected));
            different |= diff;
        }

    QVERIFY(!different);
}
#endif

void tst_QPainter::saveAndRestore_data()
{
    QTest::addColumn<QFont>("font");
    QTest::addColumn<QPen>("pen");
    QTest::addColumn<QBrush>("brush");
    QTest::addColumn<QColor>("backgroundColor");
    QTest::addColumn<int>("backgroundMode");
    QTest::addColumn<QPoint>("brushOrigin");
    QTest::addColumn<QRegion>("clipRegion");
    QTest::addColumn<QRect>("window");
    QTest::addColumn<QRect>("viewport");

    QPixmap pixmap(1, 1);
    QPainter p(&pixmap);
    QFont font = p.font();
    QPen pen = p.pen();
    QBrush brush = p.brush();
    QColor backgroundColor = p.background().color();
    Qt::BGMode backgroundMode = p.backgroundMode();
    QPoint brushOrigin = p.brushOrigin();
    QRegion clipRegion = p.clipRegion();
    QRect window = p.window();
    QRect viewport = p.viewport();

    QTest::newRow("Original") << font << pen << brush << backgroundColor << int(backgroundMode)
            << brushOrigin << clipRegion << window << viewport;

    QFont font2 = font;
    font2.setPointSize( 24 );
    QTest::newRow("Modified font.pointSize, brush, backgroundColor, backgroundMode")
            << font2 << pen << QBrush(Qt::red) << QColor(Qt::blue) << int(Qt::TransparentMode)
            << brushOrigin << clipRegion << window << viewport;

    font2 = font;
    font2.setPixelSize( 20 );
    QTest::newRow("Modified font.pixelSize, brushOrigin, pos")
            << font2 << pen << brush << backgroundColor << int(backgroundMode)
            << QPoint( 50, 32 ) << clipRegion << window << viewport;

    QTest::newRow("Modified clipRegion, window, viewport")
            << font << pen << brush << backgroundColor << int(backgroundMode)
            << brushOrigin << clipRegion.subtracted(QRect(10,10,50,30))
            << QRect(-500, -500, 500, 500 ) << QRect( 0, 0, 50, 50 );
}

void tst_QPainter::saveAndRestore()
{
    QFETCH( QFont, font );
    QFETCH( QPen, pen );
    QFETCH( QBrush, brush );
    QFETCH( QColor, backgroundColor );
    QFETCH( int, backgroundMode );
    QFETCH( QPoint, brushOrigin );
    QFETCH( QRegion, clipRegion );
    QFETCH( QRect, window );
    QFETCH( QRect, viewport );

    QPixmap pixmap(1, 1);
    QPainter painter(&pixmap);

    QFont font_org = painter.font();
    QPen pen_org = painter.pen();
    QBrush brush_org = painter.brush();
    QColor backgroundColor_org = painter.background().color();
    Qt::BGMode backgroundMode_org = painter.backgroundMode();
    QPoint brushOrigin_org = painter.brushOrigin();
    QRegion clipRegion_org = painter.clipRegion();
    QRect window_org = painter.window();
    QRect viewport_org = painter.viewport();

    painter.save();
    painter.setFont( font );
    painter.setPen( QPen(pen) );
    painter.setBrush( brush );
    painter.setBackground( backgroundColor );
    painter.setBackgroundMode( (Qt::BGMode)backgroundMode );
    painter.setBrushOrigin( brushOrigin );
    painter.setClipRegion( clipRegion );
    painter.setWindow( window );
    painter.setViewport( viewport );
    painter.restore();

    QCOMPARE( painter.font(), font_org );
    QCOMPARE( painter.font().pointSize(), font_org.pointSize() );
    QCOMPARE( painter.font().pixelSize(), font_org.pixelSize() );
    QCOMPARE( painter.pen(), pen_org );
    QCOMPARE( painter.brush(), brush_org );
    QCOMPARE( painter.background().color(), backgroundColor_org );
    QCOMPARE( painter.backgroundMode(), backgroundMode_org );
    QCOMPARE( painter.brushOrigin(), brushOrigin_org );
    QCOMPARE( painter.clipRegion(), clipRegion_org );
    QCOMPARE( painter.window(), window_org );
    QCOMPARE( painter.viewport(), viewport_org );
}

/*
   Helper functions
*/

QColor tst_QPainter::baseColor( int k, int intensity )
{
    int r = ( k & 1 ) * intensity;
    int g = ( (k>>1) & 1 ) * intensity;
    int b = ( (k>>2) & 1 ) * intensity;
    return QColor( r, g, b );
}

QImage tst_QPainter::getResImage( const QString &dir, const QString &addition, const QString &extension )
{
    QImage res;
    QString resFilename  = dir + QLatin1String("/res_") + addition + QLatin1Char('.') + extension;
    if ( !res.load( resFilename ) ) {
        qWarning() << "Could not load result data" << resFilename;
        return QImage();
    }
    return res;
}

QBitmap tst_QPainter::getBitmap( const QString &dir, const QString &filename, bool mask )
{
    QBitmap bm;
    QString bmFilename = dir + QLatin1Char('/') + filename + QLatin1String(".xbm");
    if ( !bm.load( bmFilename ) ) {
        qWarning() << "Could not load bitmap" << bmFilename;
        return QBitmap();
    }
    if ( mask ) {
        QBitmap mask;
        QString maskFilename = dir + QLatin1Char('/') + filename + QLatin1String("-mask.xbm");
        if (!mask.load(maskFilename)) {
            qWarning() << "Could not load mask" << maskFilename;
            return QBitmap();
        }
        bm.setMask( mask );
    }
    return bm;
}

static int getPaintedPixels(const QImage &image, const QColor &background)
{
    uint color = background.rgba();

    int pixels = 0;

    for (int y = 0; y < image.height(); ++y)
        for (int x = 0; x < image.width(); ++x)
            if (image.pixel(x, y) != color)
                ++pixels;

    return pixels;
}

static QRect getPaintedSize(const QImage &image, const QColor &background)
{
    // not the fastest but at least it works..
    int xmin = image.width() + 1;
    int xmax = -1;
    int ymin = image.height() +1;
    int ymax = -1;

    uint color = background.rgba();

    for ( int y = 0; y < image.height(); ++y ) {
        for (int x = 0; x < image.width(); ++x) {
            QRgb pixel = image.pixel( x, y );
            if (pixel != color && x < xmin)
                xmin = x;
            if (pixel != color && x > xmax)
                xmax = x;
            if (pixel != color && y < ymin)
                ymin = y;
            if (pixel != color && y > ymax)
                ymax = y;
        }
    }

    return QRect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
}

static QRect getPaintedSize(const QPixmap &pm, const QColor &background)
{
    return getPaintedSize(pm.toImage(), background);
}

#ifndef QT_NO_WIDGETS

void tst_QPainter::drawBorderPixmap()
{
    QPixmap src(79,79);
    src.fill(Qt::transparent);

    QImage pm(200,200,QImage::Format_RGB32);
    QPainter p(&pm);
    p.setTransform(QTransform(-1,0,0,-1,173.5,153.5));
    qDrawBorderPixmap(&p, QRect(0,0,75,105), QMargins(39,39,39,39), src, QRect(0,0,79,79), QMargins(39,39,39,39),
                       QTileRules(Qt::StretchTile,Qt::StretchTile), { });
}
#endif

void tst_QPainter::drawPixmapFragments()
{
    QPixmap origPixmap(20, 20);
    QPixmap resPixmap(20, 20);
    QPainter::PixmapFragment fragments[4] = { {15, 15,  0,  0, 10, 10, 1, 1, 0, 1},
                                              { 5, 15, 10,  0, 10, 10, 1, 1, 0, 1},
                                              {15,  5,  0, 10, 10, 10, 1, 1, 0, 1},
                                              { 5,  5, 10, 10, 10, 10, 1, 1, 0, 1} };
    {
        QPainter p(&origPixmap);
        p.fillRect(0, 0, 10, 10, Qt::red);
        p.fillRect(10, 0, 10, 10, Qt::green);
        p.fillRect(0, 10, 10, 10, Qt::blue);
        p.fillRect(10, 10, 10, 10, Qt::yellow);
    }
    {
        QPainter p(&resPixmap);
        p.drawPixmapFragments(fragments, 4, origPixmap);
    }

    QImage origImage = origPixmap.toImage().convertToFormat(QImage::Format_ARGB32);
    QImage resImage = resPixmap.toImage().convertToFormat(QImage::Format_ARGB32);

    QCOMPARE(resImage.size(), resPixmap.size());
    QVERIFY(resImage.pixel(5, 5) == origImage.pixel(15, 15));
    QVERIFY(resImage.pixel(5, 15) == origImage.pixel(15, 5));
    QVERIFY(resImage.pixel(15, 5) == origImage.pixel(5, 15));
    QVERIFY(resImage.pixel(15, 15) == origImage.pixel(5, 5));


    QPainter::PixmapFragment fragment = QPainter::PixmapFragment::create(QPointF(20, 20), QRectF(30, 30, 2, 2));
    QCOMPARE(fragment.x, qreal(20));
    QCOMPARE(fragment.y, qreal(20));
    QCOMPARE(fragment.sourceLeft, qreal(30));
    QCOMPARE(fragment.sourceTop, qreal(30));
    QCOMPARE(fragment.width, qreal(2));
    QCOMPARE(fragment.height, qreal(2));
    QCOMPARE(fragment.scaleX, qreal(1));
    QCOMPARE(fragment.scaleY, qreal(1));
    QCOMPARE(fragment.rotation, qreal(0));
    QCOMPARE(fragment.opacity, qreal(1));
}

void tst_QPainter::drawPixmapNegativeScale()
{
    // basePixmap is a 16x16 opaque white square ...
    QPixmap basePixmap(16, 16);
    basePixmap.fill(QColor(255, 255, 255, 255));
    // ... with an opaque black 8x16 left strip
    QPainter p(&basePixmap);
    p.setCompositionMode(QPainter::CompositionMode_Source);
    p.fillRect(QRect(0, 0, 8, 16), QColor(0, 0, 0, 255));
    p.end();

    // verify one pixel value for each strip
    QImage baseImage = basePixmap.toImage().convertToFormat(QImage::Format_ARGB32);
    QVERIFY(baseImage.pixel(4, 8) == qRgba(0, 0, 0, 255));
    QVERIFY(baseImage.pixel(12, 8) == qRgba(255, 255, 255, 255));

    // resultPixmap is a 16x16 square
    QPixmap resultPixmap(16, 16);

    // draw basePixmap over resultPixmap using x=-1.0 y=-1.0
    // scaling factors (i.e. 180° rotation)
    QPainter p2(&resultPixmap);
    p2.setCompositionMode(QPainter::CompositionMode_Source);
    p2.scale(qreal(-1.0), qreal(-1.0));
    p2.translate(-resultPixmap.width(), -resultPixmap.height());
    p2.drawPixmap(resultPixmap.rect(), basePixmap);
    p2.end();

    // check result
    QImage resultImage = resultPixmap.toImage().convertToFormat(QImage::Format_ARGB32);
    QVERIFY(resultImage.pixel(4, 8) == qRgba(255, 255, 255, 255)); // left strip is now white
    QVERIFY(resultImage.pixel(12, 8) == qRgba(0, 0, 0, 255)); // and right strip is now black
}

void tst_QPainter::drawPixmapRounding()
{
    // Just test that we don't assert
    QBitmap bm(8, 8);
    QImage out(64, 64, QImage::Format_RGB32);
    QPainter p(&out);
    qreal y = 26.499999999999996;
    p.drawPixmap(QPointF(0, y), bm);
}

void tst_QPainter::drawLine_data()
{
    QTest::addColumn<QLine>("line");

    QTest::newRow("0-45") << QLine(0, 20, 100, 0);
    QTest::newRow("45-90") << QLine(0, 100, 20, 0);
    QTest::newRow("90-135") << QLine(20, 100, 0, 0);
    QTest::newRow("135-180") << QLine(100, 20, 0, 0);
    QTest::newRow("180-225") << QLine(100, 0, 0, 20);
    QTest::newRow("225-270") << QLine(20, 0, 0, 100);
    QTest::newRow("270-315") << QLine(0, 0, 20, 100);
    QTest::newRow("315-360") << QLine(0, 0, 100, 20);
}

void tst_QPainter::drawLine()
{
    const int offset = 5;
    const int epsilon = 1; // allow for one pixel difference

    QFETCH(QLine, line);

    QPixmap pixmapUnclipped(qMin(line.x1(), line.x2())
                            + 2*offset + qAbs(line.dx()),
                            qMin(line.y1(), line.y2())
                            + 2*offset + qAbs(line.dy()));

    { // unclipped
        pixmapUnclipped.fill(Qt::white);
        QPainter p(&pixmapUnclipped);
        p.translate(offset, offset);
        p.setPen(QPen(Qt::black));
        p.drawLine(line);
        p.end();

        const QRect painted = getPaintedSize(pixmapUnclipped, Qt::white);

        QLine l = line;
        l.translate(offset, offset);
        QVERIFY(qAbs(painted.width() - qAbs(l.dx())) <= epsilon);
        QVERIFY(qAbs(painted.height() - qAbs(l.dy())) <= epsilon);
        QVERIFY(qAbs(painted.top() - qMin(l.y1(), l.y2())) <= epsilon);
        QVERIFY(qAbs(painted.left() - qMin(l.x1(), l.x2())) <= epsilon);
        QVERIFY(qAbs(painted.bottom() - qMax(l.y1(), l.y2())) <= epsilon);
        QVERIFY(qAbs(painted.right() - qMax(l.x1(), l.x2())) <= epsilon);
    }

    QPixmap pixmapClipped(qMin(line.x1(), line.x2())
                          + 2*offset + qAbs(line.dx()),
                          qMin(line.y1(), line.y2())
                          + 2*offset + qAbs(line.dy()));
    { // clipped
        const QRect clip = QRect::span(line.p1(), line.p2());

        pixmapClipped.fill(Qt::white);
        QPainter p(&pixmapClipped);
        p.translate(offset, offset);
        p.setClipRect(clip);
        p.setPen(QPen(Qt::black));
        p.drawLine(line);
        p.end();
    }

    const QImage unclipped = pixmapUnclipped.toImage();
    const QImage clipped = pixmapClipped.toImage();
    QCOMPARE(unclipped, clipped);
}

void tst_QPainter::drawLine_clipped()
{
    QImage image(16, 1, QImage::Format_ARGB32_Premultiplied);
    image.fill(0x0);

    QPainter p(&image);
    p.setPen(QPen(Qt::black, 10));

    // this should fill the whole image
    p.drawLine(-1, -1, 17, 1);
    p.end();

    for (int x = 0; x < 16; ++x)
        QCOMPARE(image.pixel(x, 0), 0xff000000);
}

void tst_QPainter::drawLine_task121143()
{
    QPen pen(Qt::black);

    QImage image(5, 5, QImage::Format_ARGB32_Premultiplied);
    image.fill(0xffffffff);
    QPainter p(&image);
    p.setPen(pen);
    p.drawLine(QLine(0, 0+4, 0+4, 0));
    p.end();

    QImage expected(5, 5, QImage::Format_ARGB32_Premultiplied);
    expected.fill(0xffffffff);
    for (int x = 0; x < 5; ++x)
        expected.setPixel(x, 5-x-1, pen.color().rgb());

    QCOMPARE(image, expected);
}

void tst_QPainter::drawLine_task190634()
{
    QPen pen(Qt::black, 3);

    QImage image(32, 32, QImage::Format_ARGB32_Premultiplied);
    QPainter p(&image);
    p.fillRect(0, 0, image.width(), image.height(), Qt::white);

    p.setPen(pen);
    p.drawLine(QLineF(2, -1.6, 10, -1.6));
    p.end();

    const uint *data = reinterpret_cast<uint *>(image.bits());

    for (int i = 0; i < image.width() * image.height(); ++i)
        QCOMPARE(data[i], 0xffffffff);

    p.begin(&image);
    p.fillRect(0, 0, image.width(), image.height(), Qt::white);

    p.setPen(pen);
    p.drawLine(QLineF(-1.6, 2, -1.6, 10));
    p.end();

    data = reinterpret_cast<uint *>(image.bits());

    for (int i = 0; i < image.width() * image.height(); ++i)
        QCOMPARE(data[i], 0xffffffff);

    p.begin(&image);
    p.fillRect(0, 0, image.width(), image.height(), Qt::white);

    p.setPen(pen);
    p.drawLine( QPoint(2,-2), QPoint(3,-5) );
    p.end();

    data = reinterpret_cast<uint *>(image.bits());

    for (int i = 0; i < image.width() * image.height(); ++i)
        QCOMPARE(data[i], 0xffffffff);
}

void tst_QPainter::drawLine_task229459()
{
    QImage image(32, 32, QImage::Format_ARGB32_Premultiplied);
    image.fill(0x0);
    QPen pen(Qt::black, 64);

    QPainter p(&image);
    p.setPen(pen);
    p.drawLine(-8, -8, 10000000, 10000000);
    p.end();

    QImage expected = image;
    expected.fill(0xff000000);

    QCOMPARE(image, expected);
}

void tst_QPainter::drawLine_task234891()
{
    QImage img(100, 1000, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);
    QImage expected = img;

    QPainter p(&img);
    p.setPen(QPen(QBrush(QColor(255,0,0)), 6));
    p.drawLine(QPointF(25000,100),QPointF(30000,105));

    p.setPen(QPen(QBrush(QColor(0,255,0)), 6));
    p.drawLine(QPointF(30000,150),QPointF(35000,155));

    p.setPen(QPen(QBrush(QColor(0,0,255)), 6));
    p.drawLine(QPointF(65000,200),QPointF(66000,205));

    QCOMPARE(expected, img);
}

void tst_QPainter::drawLine_task216948()
{
    QImage img(1, 10, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);

    QPainter p(&img);
    QLine line(10, 0, 10, 10);
    p.translate(-10, 0);
    p.drawLine(line);
    p.end();

    for (int i = 0; i < img.height(); ++i)
        QCOMPARE(img.pixel(0, i), QColor(Qt::black).rgba());
}

void tst_QPainter::drawLineEndPoints()
{
    QImage img(256, 256, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);

    QPainter p;
    for (int x = 0; x < img.width(); ++x) {
        QRgb color = qRgb(x, 0, 0);
        p.begin(&img);
        p.setPen(QPen(color));
        p.drawLine(x, 0, 255 - x, 255);
        p.end();
        QCOMPARE(img.pixel(x, 0), color);
        QCOMPARE(img.pixel(255 - x, 255), color);
    }
    for (int y = 0; y < img.height(); ++y) {
        QRgb color = qRgb(0, y, 0);
        p.begin(&img);
        p.setPen(QPen(color));
        p.drawLine(0, y, 255, 255 - y);
        p.end();
        QCOMPARE(img.pixel(0, y), color);
        QCOMPARE(img.pixel(255, 255 - y), color);
    }
    for (int x = 0; x < img.width(); ++x) {
        QRgb color = qRgb(x, 0, x);
        p.begin(&img);
        p.setPen(QPen(color));
        p.drawLine(x, 255, 255 - x, 0);
        p.end();
        QCOMPARE(img.pixel(x, 255), color);
        QCOMPARE(img.pixel(255 - x, 0), color);
    }
    for (int y = 0; y < img.height(); ++y) {
        QRgb color = qRgb(0, y, y);
        p.begin(&img);
        p.setPen(QPen(color));
        p.drawLine(255, y, 0, 255 - y);
        p.end();
        QCOMPARE(img.pixel(255, y), color);
        QCOMPARE(img.pixel(0, 255 - y), color);
    }
}

void tst_QPainter::drawRect()
{
    QFETCH(QRect, rect);
    QFETCH(bool, usePen);

    QPixmap pixmap(rect.x() + rect.width() + 10,
                   rect.y() + rect.height() + 10);
    {
        pixmap.fill(Qt::white);
        QPainter p(&pixmap);
        p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen));
        p.setBrush(Qt::black);
        p.drawRect(rect);
        p.end();

        int increment = usePen ? 1 : 0;

        const QRect painted = getPaintedSize(pixmap, Qt::white);
        QCOMPARE(painted.width(), rect.width() + increment);
        QCOMPARE(painted.height(), rect.height() + increment);
    }
}

void tst_QPainter::drawRect2()
{
    QImage image(64, 64, QImage::Format_ARGB32_Premultiplied);
    {
        image.fill(0xffffffff);

        QTransform transform(0.368567, 0, 0, 0, 0.368567, 0, 0.0289, 0.0289, 1);

        QPainter p(&image);
        p.setTransform(transform);
        p.setBrush(Qt::red);
        p.setPen(Qt::NoPen);
        p.drawRect(QRect(14, 14, 39, 39));
        p.end();

        QRect fill = getPaintedSize(image, Qt::white);
        image.fill(0xffffffff);

        p.begin(&image);
        p.setTransform(transform);
        p.drawRect(QRect(14, 14, 39, 39));
        p.end();

        QRect stroke = getPaintedSize(image, Qt::white);
        QCOMPARE(stroke.adjusted(1, 1, 0, 0), fill.adjusted(1, 1, 0, 0));
    }
}

void tst_QPainter::fillRect_data()
{
    QTest::addColumn<QImage::Format>("format");

    QTest::newRow("argb32pm") << QImage::Format_ARGB32_Premultiplied;
    QTest::newRow("rgba8888pm") << QImage::Format_RGBA8888_Premultiplied;
    QTest::newRow("rgba64pm") << QImage::Format_RGBA64_Premultiplied;
    QTest::newRow("rgbaFP16pm") << QImage::Format_RGBA16FPx4_Premultiplied;
}

void tst_QPainter::fillRect()
{
    QFETCH(QImage::Format, format);

    QImage image(100, 100, format);
    image.fill(QColor(0, 0, 0, 0).rgba());

    QPainter p(&image);

    p.fillRect(0, 0, 100, 100, QColor(255, 0, 0, 127));

//    pixmap.save("bla1.png", "PNG");
    QCOMPARE(getPaintedSize(image, QColor(0, 0, 0, 0)),
             QRect(0, 0, 100, 100));
    QCOMPARE(getPaintedSize(image, QColor(127, 0, 0, 127)).isValid(),
             QRect().isValid());

    p.setCompositionMode(QPainter::CompositionMode_SourceIn);
    p.fillRect(50, 0, 50, 100, QColor(0, 0, 255, 255));

    QCOMPARE(getPaintedSize(image, QColor(127, 0, 0, 127)),
             QRect(50, 0, 50, 100));
    QCOMPARE(getPaintedSize(image, QColor(0, 0, 127, 127)),
             QRect(0, 0, 50, 100));
}

void tst_QPainter::fillRect2_data()
{
    QTest::addColumn<QImage::Format>("format");

    QTest::newRow("argb32") << QImage::Format_ARGB32;
    QTest::newRow("argb32pm") << QImage::Format_ARGB32_Premultiplied;
    QTest::newRow("rgba8888") << QImage::Format_RGBA8888;
    QTest::newRow("rgba8888pm") << QImage::Format_RGBA8888_Premultiplied;
    QTest::newRow("a2rgb30pm") << QImage::Format_A2RGB30_Premultiplied;
    QTest::newRow("a2bgr30pm") << QImage::Format_A2BGR30_Premultiplied;
}

void tst_QPainter::fillRect2()
{
    QFETCH(QImage::Format, format);

    QRgb background = 0x0;

    QImage img(1, 20, format);
    img.fill(background);

    QPainter p(&img);

    QRectF rect(0, 1, 1.2, 18);
    p.fillRect(rect, Qt::yellow);

    p.end();

    QCOMPARE(img.pixel(0, 0), background);
    QCOMPARE(img.pixel(0, img.height() - 1), background);

    QCOMPARE(img.pixel(0, 1), img.pixel(0, 2));
    QCOMPARE(img.pixel(0, img.height() - 2), img.pixel(0, img.height() - 3));
    QCOMPARE(img.pixel(0, 1), QColor(Qt::yellow).rgba());
}

void tst_QPainter::fillRect3()
{
    QFETCH(QImage::Format, format);

    QImage img(1, 1, format);
    img.fill(QColor(Qt::black).rgba());

    QPainter p(&img);
    p.setCompositionMode(QPainter::CompositionMode_Source);
    p.fillRect(img.rect(), Qt::transparent);
    p.end();

    QCOMPARE(img.pixel(0, 0), 0U);
}

void tst_QPainter::fillRect4()
{
    QFETCH(QImage::Format, format);

    QImage image(100, 1, format);
    image.fill(0x0);

    QImage expected = image;
    expected.fill(0xffffffff);

    QPainter p(&image);
    p.scale(1.1, 1);
    p.setPen(Qt::NoPen);

    for (int i = 0; i < 33; ++i)
        p.fillRect(QRectF(3 * i, 0, 3, 1), Qt::white);

    p.end();

    QCOMPARE(image, expected);
}

void tst_QPainter::fillRectNonPremul_data()
{
    QTest::addColumn<QImage::Format>("format");
    QTest::addColumn<uint>("color");

    QTest::newRow("argb32 7f1f3f7f") << QImage::Format_ARGB32 << qRgba(31, 63, 127, 127);
    QTest::newRow("rgba8888 7f1f3f7f") << QImage::Format_RGBA8888 << qRgba(31, 63, 127, 127);

    QTest::newRow("argb32 3f1f3f7f") << QImage::Format_ARGB32 << qRgba(31, 63, 127, 63);
    QTest::newRow("rgba8888 3f1f3f7f") << QImage::Format_RGBA8888 << qRgba(31, 63, 127, 63);

    QTest::newRow("argb32 070375f4") << QImage::Format_ARGB32 << qRgba(3, 117, 244, 7);
    QTest::newRow("rgba8888 070375f4") << QImage::Format_RGBA8888 << qRgba(3, 117, 244, 7);

    QTest::newRow("argb32 0301fe0c") << QImage::Format_ARGB32 << qRgba(1, 254, 12, 3);
    QTest::newRow("rgba8888 0301fe0c") << QImage::Format_RGBA8888 << qRgba(1, 254, 12, 3);

    QTest::newRow("argb32 01804010") << QImage::Format_ARGB32 << qRgba(128, 64, 32, 1);
    QTest::newRow("rgba8888 01804010") << QImage::Format_RGBA8888 << qRgba(128, 64, 32, 1);
}

void tst_QPainter::fillRectNonPremul()
{
    QFETCH(QImage::Format, format);
    QFETCH(uint, color);

    QImage image(1, 1, format);
    QRectF rect(0, 0, 1, 1);

    // Fill with CompositionMode_SourceOver tests blend_color
    image.fill(Qt::transparent);
    QPainter painter(&image);
    painter.fillRect(rect, QColor::fromRgba(color));
    painter.end();

    // Fill with CompositionMode_Source tests rectfill.
    painter.begin(&image);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.fillRect(rect, QColor::fromRgba(color));
    painter.end();

    QRgb p = image.pixel(0, 0);
    QCOMPARE(qAlpha(p), qAlpha(color));
    QVERIFY(qAbs(qRed(p)-qRed(color)) <= 1);
    QVERIFY(qAbs(qGreen(p)-qGreen(color)) <= 1);
    QVERIFY(qAbs(qBlue(p)-qBlue(color)) <= 1);
}

void tst_QPainter::fillRectRGB30_data()
{
    QTest::addColumn<uint>("color");

    QTest::newRow("17|43|259") << (0xc0000000 | (17 << 20) | (43 << 10) | 259);
    QTest::newRow("2|33|444") << (0xc0000000 | (2 << 20) | (33 << 10) | 444);
    QTest::newRow("1000|1000|1000") << (0xc0000000 | (1000 << 20) | (1000 << 10) | 1000);
}

void tst_QPainter::fillRectRGB30()
{
    QFETCH(uint, color);
    QRectF rect(0, 0, 1, 1);

    // Fill with CompositionMode_SourceOver tests blend_color
    QImage image1(1, 1, QImage::Format_A2BGR30_Premultiplied);
    image1.fill(Qt::transparent);
    QPainter painter(&image1);
    painter.fillRect(rect, QColor::fromRgba64(qConvertA2rgb30ToRgb64<PixelOrderBGR>(color)));
    painter.end();

    uint pixel1 = ((const uint*)(image1.bits()))[0];
    QCOMPARE(pixel1, color);

    // Fill with CompositionMode_Source tests rectfill.
    QImage image2(1, 1, QImage::Format_RGB30);
    painter.begin(&image2);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.fillRect(rect, QColor::fromRgba64(qConvertA2rgb30ToRgb64<PixelOrderRGB>(color)));
    painter.end();

    uint pixel2 = ((const uint*)(image2.bits()))[0];
    QCOMPARE(pixel2, color);
}

void tst_QPainter::drawPath_data()
{
    QTest::addColumn<QPainterPath>("path");
    QTest::addColumn<QRect>("expectedBounds");
    QTest::addColumn<int>("expectedPixels");

    {
        QPainterPath p;
        p.addRect(2, 2, 10, 10);
        QTest::newRow("int-aligned rect") << p << QRect(2, 2, 10, 10) << 10 * 10;
    }

    {
        QPainterPath p;
        p.addRect(2.25, 2.25, 10, 10);
        QTest::newRow("non-aligned rect") << p << QRect(2, 2, 10, 10) << 10 * 10;
    }

    {
        QPainterPath p;
        p.addRect(2.25, 2.25, 10.5, 10.5);
        QTest::newRow("non-aligned rect 2") << p << QRect(2, 2, 11, 11) << 11 * 11;
    }

    {
        QPainterPath p;
        p.addRect(2.5, 2.5, 10, 10);
        QTest::newRow("non-aligned rect 3") << p << QRect(3, 3, 10, 10) << 10 * 10;
    }

    {
        QPainterPath p;
        p.addRect(2, 2, 10, 10);
        p.addRect(4, 4, 6, 6);
        QTest::newRow("rect-in-rect") << p << QRect(2, 2, 10, 10) << 10 * 10 - 6 * 6;
    }

    {
        QPainterPath p;
        p.addRect(2, 2, 10, 10);
        p.addRect(4, 4, 6, 6);
        p.addRect(6, 6, 2, 2);
        QTest::newRow("rect-in-rect-in-rect") << p << QRect(2, 2, 10, 10) << 10 * 10 - 6 * 6 + 2 * 2;
    }
}

void tst_QPainter::drawPath()
{
    QFETCH(QPainterPath, path);
    QFETCH(QRect, expectedBounds);
    QFETCH(int, expectedPixels);

    const int offset = 2;

    QImage image(expectedBounds.width() + 2 * offset, expectedBounds.height() + 2 * offset,
                 QImage::Format_ARGB32_Premultiplied);
    image.fill(QColor(Qt::white).rgb());

    QPainter p(&image);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::black);
    p.translate(offset - expectedBounds.left(), offset - expectedBounds.top());
    p.drawPath(path);
    p.end();

    const QRect paintedBounds = getPaintedSize(image, Qt::white);

    QCOMPARE(paintedBounds.x(), offset);
    QCOMPARE(paintedBounds.y(), offset);
    QCOMPARE(paintedBounds.width(), expectedBounds.width());
    QCOMPARE(paintedBounds.height(), expectedBounds.height());

    if (expectedPixels != -1) {
        int paintedPixels = getPaintedPixels(image, Qt::white);
        QCOMPARE(paintedPixels, expectedPixels);
    }
}

void tst_QPainter::drawPath2()
{
    const int w = 50;

    for (int h = 5; h < 200; ++h) {
        QPainterPath p1, p2;
        p1.lineTo(w, 0);
        p1.lineTo(w, h);

        p2.lineTo(w, h);
        p2.lineTo(0, h);

        const int offset = 2;

        QImage image(w + 2 * offset, h + 2 * offset,
                     QImage::Format_ARGB32_Premultiplied);
        image.fill(QColor(Qt::white).rgb());

        QPainter p(&image);
        p.setPen(Qt::NoPen);
        p.setBrush(Qt::black);
        p.translate(offset, offset);
        p.drawPath(p1);
        p.end();

        const int p1Pixels = getPaintedPixels(image, Qt::white);

        image.fill(QColor(Qt::white).rgb());
        p.begin(&image);
        p.setPen(Qt::NoPen);
        p.setBrush(Qt::black);
        p.translate(offset, offset);
        p.drawPath(p2);
        p.end();

        const int p2Pixels = getPaintedPixels(image, Qt::white);

        QCOMPARE(p1Pixels + p2Pixels, w * h);
    }
}

void tst_QPainter::drawPath3()
{
    QImage imgA(100, 100, QImage::Format_RGB32);
    imgA.fill(0xffffff);
    QImage imgB = imgA;

    QPainterPath path;
    for (int y = 0; y < imgA.height(); ++y) {
        for (int x = 0; x < imgA.width(); ++x) {
            if ((x + y) & 1) {
                imgA.setPixel(x, y, 0);
                path.addRect(x, y, 1, 1);
            }
        }
    }

    QPainter p(&imgB);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::black);

    p.drawPath(path);
    p.end();

    QCOMPARE(imgA, imgB);

    imgA.invertPixels();
    imgB.fill(0xffffff);

    p.begin(&imgB);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::black);

    QRectF rect(0, 0, imgA.width(), imgA.height());
    path.addRect(rect.adjusted(-10, -10, 10, 10));
    p.drawPath(path);
    p.end();

    QCOMPARE(imgA, imgB);

    path.setFillRule(Qt::WindingFill);
    imgB.fill(0xffffff);

    p.begin(&imgB);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::black);
    QRect clip = rect.adjusted(10, 10, -10, -10).toRect();
    p.setClipRect(clip);
    p.drawPath(path);
    p.end();

    QCOMPARE(getPaintedPixels(imgB, Qt::white), clip.width() * clip.height());
}

void tst_QPainter::drawEllipse_data()
{
    QTest::addColumn<QSize>("size");
    QTest::addColumn<bool>("usePen");

    // The current drawEllipse algorithm (drawEllipse_midpoint_i in
    // qpaintengine_raster.cpp) draws ellipses that are too wide if the
    // ratio between width and hight is too large/small (task 114874). Those
    // ratios are therefore currently avoided.
    for (int w = 10; w < 128; w += 7) {
        const QByteArray wB = QByteArray::number(w);
        for (int h = w/2; h < qMin(2*w, 128); h += 13) {
            const QByteArray sB = wB + 'x' + QByteArray::number(h);
            QTest::newRow((sB + " with pen").constData()) << QSize(w, h) << true;
            QTest::newRow((sB + " no pen").constData()) << QSize(w, h) << false;
        }
    }
}

void tst_QPainter::drawEllipse()
{
    QFETCH(QSize, size);
    QFETCH(bool, usePen);

    const int offset = 10;
    QRect rect(QPoint(offset, offset), size);

    QImage image(size.width() + 2 * offset, size.height() + 2 * offset,
                 QImage::Format_ARGB32_Premultiplied);
    image.fill(QColor(Qt::white).rgb());

    QPainter p(&image);
    p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen));
    p.setBrush(Qt::black);
    p.drawEllipse(rect);
    p.end();

    QPixmap pixmap = QPixmap::fromImage(image);

    const QRect painted = getPaintedSize(pixmap, Qt::white);

    QCOMPARE(painted.x(), rect.x());
    QCOMPARE(painted.y(), rect.y() + (usePen ? 0 : 1));
    QCOMPARE(painted.width(), size.width() + (usePen ? 1 : 0));
    QCOMPARE(painted.height(), size.height() + (usePen ? 1 : -1));
}

void tst_QPainter::drawClippedEllipse_data()
{
    QTest::addColumn<QRect>("rect");

    for (int w = 20; w < 128; w += 7) {
        const QByteArray wB = QByteArray::number(w);
        for (int h = w/2; h < qMin(2*w, 128); h += 13) {
            const QByteArray sB = wB + 'x' + QByteArray::number(h);
            QTest::newRow((sB + " top").constData()) << QRect(0, -h/2, w, h);
            QTest::newRow((sB + " topright").constData()) << QRect(w/2, -h/2, w, h);
            QTest::newRow((sB + " right").constData()) << QRect(w/2, 0, w, h);
            QTest::newRow((sB + " bottomright").constData()) << QRect(w/2, h/2, w, h);
            QTest::newRow((sB + " bottom").constData()) << QRect(0, h/2, w, h);
            QTest::newRow((sB + " bottomleft").constData()) << QRect(-w/2, h/2, w, h);
            QTest::newRow((sB + " left").constData()) << QRect(-w/2, 0, w, h);
            QTest::newRow((sB + " topleft").constData()) << QRect(-w/2, -h/2, w, h);
        }
    }
}

void tst_QPainter::drawClippedEllipse()
{
    QFETCH(QRect, rect);
    if (sizeof(qreal) != sizeof(double))
        QSKIP("Test only works for qreal==double");
    QImage image(rect.width() + 1, rect.height() + 1,
                 QImage::Format_ARGB32_Premultiplied);
    QRect expected = QRect(rect.x(), rect.y(), rect.width()+1, rect.height()+1)
                     & QRect(0, 0, image.width(), image.height());


    image.fill(QColor(Qt::white).rgb());
    QPainter p(&image);
    p.drawEllipse(rect);
    p.end();

    QPixmap pixmap = QPixmap::fromImage(image);
    const QRect painted = getPaintedSize(pixmap, Qt::white);

    QCOMPARE(painted.x(), expected.x());
    QCOMPARE(painted.y(), expected.y());
    QCOMPARE(painted.width(), expected.width());
    QCOMPARE(painted.height(), expected.height());

}

void tst_QPainter::drawRoundedRect()
{
    QFETCH(QRect, rect);
    QFETCH(bool, usePen);

#ifdef Q_OS_DARWIN
    if (QTest::currentDataTag() == QByteArray("rect(6, 12, 3, 14) with pen") ||
        QTest::currentDataTag() == QByteArray("rect(6, 17, 3, 25) with pen") ||
        QTest::currentDataTag() == QByteArray("rect(10, 6, 10, 3) with pen") ||
        QTest::currentDataTag() == QByteArray("rect(10, 12, 10, 14) with pen") ||
        QTest::currentDataTag() == QByteArray("rect(13, 45, 17, 80) with pen") ||
        QTest::currentDataTag() == QByteArray("rect(13, 50, 17, 91) with pen") ||
        QTest::currentDataTag() == QByteArray("rect(17, 6, 24, 3) with pen") ||
        QTest::currentDataTag() == QByteArray("rect(24, 12, 38, 14) with pen"))
        QSKIP("The Mac paint engine is off-by-one on certain rect sizes");
#endif
    QPixmap pixmap(rect.x() + rect.width() + 10,
                   rect.y() + rect.height() + 10);
    {
        pixmap.fill(Qt::white);
        QPainter p(&pixmap);
        p.setPen(usePen ? QPen(Qt::black) : QPen(Qt::NoPen));
        p.setBrush(Qt::black);
        p.drawRoundedRect(rect, 25, 25, Qt::RelativeSize);
        p.end();

        int increment = usePen ? 1 : 0;

        const QRect painted = getPaintedSize(pixmap, Qt::white);
        QCOMPARE(painted.width(), rect.width() + increment);
        QCOMPARE(painted.height(), rect.height() + increment);
    }
}

void tst_QPainter::qimageFormats_data()
{
    QTest::addColumn<QImage::Format>("format");
    QTest::newRow("QImage::Format_RGB32") << QImage::Format_RGB32;
    QTest::newRow("QImage::Format_ARGB32") << QImage::Format_ARGB32;
    QTest::newRow("QImage::Format_ARGB32_Premultiplied") << QImage::Format_ARGB32_Premultiplied;
    QTest::newRow("QImage::Format_RGB16") << QImage::Format_RGB16;
    QTest::newRow("Qimage::Format_ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied;
    QTest::newRow("Qimage::Format_RGB666") << QImage::Format_RGB666;
    QTest::newRow("Qimage::Format_RGB555") << QImage::Format_RGB555;
    QTest::newRow("Qimage::Format_ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied;
    QTest::newRow("Qimage::Format_RGB888") << QImage::Format_RGB888;
    QTest::newRow("Qimage::Format_BGR888") << QImage::Format_BGR888;
    QTest::newRow("Qimage::Format_A2RGB30_Premultiplied") << QImage::Format_A2RGB30_Premultiplied;
    QTest::newRow("Qimage::Format_RGB30") << QImage::Format_RGB30;
    QTest::newRow("QImage::Format_RGBX16FPx4") << QImage::Format_RGBX16FPx4;
    QTest::newRow("QImage::Format_RGBA32FPx4_Premultiplied") << QImage::Format_RGBA32FPx4_Premultiplied;
}

/*
    Tests that QPainter can paint on various QImage formats.
*/
void tst_QPainter::qimageFormats()
{
    QFETCH(QImage::Format, format);

    const QSize size(100, 100);
    QImage image(size, format);
    image.fill(0);

    const QColor testColor(Qt::red);
    QPainter p(&image);
    QVERIFY(p.isActive());
    p.setBrush(QBrush(testColor));
    p.drawRect(QRect(QPoint(0,0), size));
    QCOMPARE(image.pixel(50, 50), testColor.rgb());
}

void tst_QPainter::fillData()
{
    QTest::addColumn<QRect>("rect");
    QTest::addColumn<bool>("usePen");

    for (int w = 3; w < 50; w += 7) {
        const QByteArray wB = QByteArray::number(w);
        for (int h = 3; h < 50; h += 11) {
            int x = w/2 + 5;
            int y = h/2 + 5;
            const QByteArray rB = "rect(" + QByteArray::number(x) + ", " + QByteArray::number(y)
                + ", " + QByteArray::number(w) + ", " + QByteArray::number(h) + ')';
            QTest::newRow((rB + " with pen").constData()) << QRect(x, y, w, h) << true;
            QTest::newRow((rB + " no pen").constData()) << QRect(x, y, w, h) << false;
        }
    }
}

/*
    Test that drawline works properly after setWindow has been called.
*/
void tst_QPainter::setWindow()
{
    QPixmap pixmap(600, 600);
    pixmap.fill(QColor(Qt::white));

    QPainter painter(&pixmap);
    painter.setWindow(0, 0, 28, 28);
    painter.drawLine(10, 10, 18, 18);

    const QRect painted = getPaintedSize(pixmap, Qt::white);
    QVERIFY(195 < painted.y() && painted.y() < 205); // correct value is around 200
    QVERIFY(195 < painted.height() && painted.height() < 205); // correct value is around 200
}

void tst_QPainter::combinedTransform()
{
    QPixmap pm(64, 64);

    QPainter p(&pm);
    p.setWindow(0, 0, 1, 1);
    p.setViewport(32, 0, 32, 32);

    p.translate(0.5, 0.5);

    QTransform ct = p.combinedTransform();
    QPointF pt = QPointF(0, 0) * ct;

    QCOMPARE(pt.x(), 48.0);
    QCOMPARE(pt.y(), 16.0);
}

void tst_QPainter::textOnTransparentImage()
{
    bool foundPixel = false;
    QImage image(10, 10, QImage::Format_ARGB32_Premultiplied);
    image.fill(qRgba(0, 0, 0, 0)); // transparent
    {
        QPainter painter(&image);
        painter.setPen(QColor(255, 255, 255));
        painter.drawText(0, 10, "W");
    }
    for (int x = 0; x < image.width(); ++x)
        for (int y = 0; y < image.height(); ++y)
            if (image.pixel(x, y) != 0)
                foundPixel = true;
    QVERIFY(foundPixel);
}

void tst_QPainter::renderHints()
{
    QImage img(1, 1, QImage::Format_RGB32);

    QPainter p(&img);

    // Turn off all...
    p.setRenderHints(QPainter::RenderHints(0xffffffff), false);
    QCOMPARE(p.renderHints(), QPainter::RenderHints{});

    // Single set/get
    p.setRenderHint(QPainter::Antialiasing);
    QVERIFY(p.renderHints() & QPainter::Antialiasing);

    p.setRenderHint(QPainter::Antialiasing, false);
    QVERIFY(!(p.renderHints() & QPainter::Antialiasing));

    // Multi set/get
    p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    QVERIFY(p.renderHints() & (QPainter::Antialiasing | QPainter::SmoothPixmapTransform));

    p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, false);
    QVERIFY(!(p.renderHints() & (QPainter::Antialiasing | QPainter::SmoothPixmapTransform)));
}

int countPixels(const QImage &img, const QRgb &color)
{
    int count = 0;
    for (int y = 0; y < img.height(); ++y) {
        for (int x = 0; x < img.width(); ++x) {
            count += ((img.pixel(x, y) & 0xffffff) == color);
        }
    }
    return count;
}

template <typename T>
void testClipping(QImage &img)
{
    QPainterPath a, b;
    a.addRect(QRect(2, 2, 4, 4));
    b.addRect(QRect(4, 4, 4, 4));
    QPainter p(&img);

    p.end();
    img.fill(0x0);
    p.begin(&img);
    p.setClipPath(a);
    p.setClipPath(b, Qt::IntersectClip);

    p.setClipping(false);
    p.setPen(Qt::NoPen);
    p.setBrush(QColor(0xff0000));
    p.drawRect(T(0, 0, 10, 10));

    p.setClipping(true);
    p.setBrush(QColor(0x00ff00));
    p.drawRect(T(0, 0, 10, 10));

    QCOMPARE(countPixels(img, 0xff0000), 96);
    QCOMPARE(countPixels(img, 0x00ff00), 4);
}

void tst_QPainter::disableEnableClipping()
{
    QImage img(10, 10, QImage::Format_RGB32);

    testClipping<QRectF>(img);
    testClipping<QRect>(img);
}

void tst_QPainter::setClipRect()
{
    QImage img(10, 10, QImage::Format_RGB32);
    // simple test to let valgrind check for buffer overflow
    {
        QPainter p(&img);
        p.setClipRect(-10, -10, 100, 100);
        p.fillRect(-10, -10, 100, 100, QBrush(QColor(Qt::red)));
    }

    // rects with negative width/height
    {
        QPainter p(&img);
        p.setClipRect(QRect(10, 10, -10, 10));
        QVERIFY(p.clipRegion().isEmpty());
        p.setClipRect(QRect(10, 10, 10, -10));
        QVERIFY(p.clipRegion().isEmpty());
        p.setClipRect(QRectF(10.5, 10.5, -10.5, 10.5));
        QVERIFY(p.clipRegion().isEmpty());
        p.setClipRect(QRectF(10.5, 10.5, 10.5, -10.5));
        QVERIFY(p.clipRegion().isEmpty());
    }

    // extreme transform, values reverse-engineered from oss-fuzz issue 406541912
    // crashed with a failed assert due to an integer overflow
    {
        QPainter p(&img);
        p.setTransform(QTransform(37.7, 0., 0.,
                                  0., 233., 0.,
                                  18.85, -163099999883.5, 1.));
        p.setClipRect(QRect(0, 0, 10, 1), Qt::ReplaceClip);
    }

    // the same extreme transform, edited to overflow on the x-axis instead
    {
        QPainter p(&img);
        p.setTransform(QTransform(233., 0., 0.,
                                  0., 37.7, 0.,
                                  -163099999883.5, 18.85, 1.));
        p.setClipRect(QRect(0, 0, 1, 10), Qt::ReplaceClip);
    }
}

/*
    Verify that the clipping works correctly.
    Just like fillRect, cliprect should snap rightwards and downwards in case of .5 coordinates.
    The red outline should be covered by the blue rect on top,
    while it should be clipped on the other edges and thus the red outline be visible

    See: QTBUG-83229, modified by QTBUG-100329
*/
void tst_QPainter::clipRect()
{
    int width = 654;
    int height = 480;
    QRect rect(0, 0, width, height);

    QImage image(width, height, QImage::Format_ARGB32);
    QPainter p(&image);
    qreal halfWidth = width / 2.0;
    qreal halfHeight = height / 2.0;

    QRectF clipRect = QRectF(halfWidth - halfWidth / 2.0, halfHeight - halfHeight / 2.0,
                             halfWidth / 2.0, halfHeight / 2.0);

    p.fillRect(rect, Qt::white);
    p.setPen(Qt::red);
    p.drawRect(clipRect);

    p.setClipRect(clipRect, Qt::ReplaceClip);
    p.fillRect(rect, Qt::blue);

    p.end();

    QCOMPARE(image.pixelColor(clipRect.left() + 1, clipRect.top()), QColor(Qt::blue));
    QCOMPARE(image.pixelColor(clipRect.left(), clipRect.top() + 1), QColor(Qt::red));
    QCOMPARE(image.pixelColor(clipRect.left() + 1, clipRect.bottom()), QColor(Qt::red));
    QCOMPARE(image.pixelColor(clipRect.right(), clipRect.top() + 1), QColor(Qt::red));
}

/*
    This tests the two different clipping approaches in QRasterPaintEngine,
    one when using a QRegion and one when using a QPainterPath. They should
    give equal results.
*/
void tst_QPainter::setEqualClipRegionAndPath_data()
{
    QTest::addColumn<QSize>("deviceSize");
    QTest::addColumn<QRegion>("region");

    QTest::newRow("empty") << QSize(100, 100) << QRegion();
    QTest::newRow("simple rect") << QSize(100, 100)
                                 << QRegion(QRect(5, 5, 10, 10));

    QList<QRect> rects;
    QRegion region;

    rects << QRect(5, 5, 10, 10) << QRect(20, 20, 10, 10);
    region.setRects(rects.constData(), rects.size());
    QTest::newRow("two rects") << QSize(100, 100) << region;

    rects.clear();
    rects << QRect(5, 5, 10, 10) << QRect(20, 5, 10, 10);
    region.setRects(rects.constData(), rects.size());
    QTest::newRow("two x-adjacent rects") << QSize(100, 100) << region;

    rects.clear();
    rects << QRect(0, 0, 10, 100) << QRect(12, 0, 10, 100);
    region.setRects(rects.constData(), rects.size());
    QTest::newRow("two x-adjacent rects 2") << QSize(100, 100) << region;

    rects.clear();
    rects << QRect(0, 0, 10, 100) << QRect(12, 0, 10, 100);
    region.setRects(rects.constData(), rects.size());
    QTest::newRow("two x-adjacent rects 3") << QSize(50, 50) << region;

    rects.clear();
    rects << QRect(0, 0, 10, 100) << QRect(12, 0, 10, 100);
    region.setRects(rects.constData(), rects.size());
    QTest::newRow("two x-adjacent rects 4") << QSize(101, 101) << region;

    region = QRegion(QRect(0, 0, 200, 200), QRegion::Ellipse);

    QTest::newRow("ellipse") << QSize(190, 200) << region;

    region ^= QRect(50, 50, 50, 50);
    QTest::newRow("ellipse 2") << QSize(200, 200) << region;
}

void tst_QPainter::setEqualClipRegionAndPath()
{
    QFETCH(QSize, deviceSize);
    QFETCH(QRegion, region);

    QPainterPath path;
    path.addRegion(region);

    QImage img1(deviceSize.width(), deviceSize.height(),
                QImage::Format_ARGB32);
    QImage img2(deviceSize.width(), deviceSize.height(),
                QImage::Format_ARGB32);
    img1.fill(0x12345678);
    img2.fill(0x12345678);

    {
        QPainter p(&img1);
        p.setClipRegion(region);
        p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red));
    }
    {
        QPainter p(&img2);
        p.setClipPath(path);
        p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red));
    }

    QCOMPARE(img1, img2);

    // rotated
    img1.fill(0x12345678);
    img2.fill(0x12345678);

    {
        QPainter p(&img1);
        p.rotate(25);
        p.setClipRegion(region);
        p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red));
    }
    {
        QPainter p(&img2);
        p.rotate(25);
        p.setClipPath(path);
        p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red));
    }

    QCOMPARE(img1, img2);

    img1.fill(0x12345678);
    img2.fill(0x12345678);

    // simple intersectclip
    img1.fill(0x12345678);
    img2.fill(0x12345678);
    {
        QPainter p(&img1);
        p.setClipRegion(region);
        p.setClipRegion(region, Qt::IntersectClip);
        p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red));
    }
    {
        QPainter p(&img2);
        p.setClipPath(path);
        p.setClipPath(path, Qt::IntersectClip);
        p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red));
    }
    QCOMPARE(img1, img2);

    img1.fill(0x12345678);
    img2.fill(0x12345678);
    {
        QPainter p(&img1);
        p.setClipPath(path);
        p.setClipRegion(region, Qt::IntersectClip);
        p.fillRect(0, 0, img1.width(), img1.height(), QColor(Qt::red));
    }
    {
        QPainter p(&img2);
        p.setClipRegion(region);
        p.setClipPath(path, Qt::IntersectClip);
        p.fillRect(0, 0, img2.width(), img2.height(), QColor(Qt::red));
    }
    QCOMPARE(img1, img2);

}

void tst_QPainter::clippedFillPath_data()
{
    QTest::addColumn<QSize>("imageSize");
    QTest::addColumn<QPainterPath>("path");
    QTest::addColumn<QRect>("clipRect");
    QTest::addColumn<QBrush>("brush");
    QTest::addColumn<QPen>("pen");

    QLinearGradient gradient(QPoint(0, 0), QPoint(100, 100));
    gradient.setColorAt(0, Qt::red);
    gradient.setColorAt(1, Qt::blue);


    QPen pen2(QColor(223, 223, 0, 223));
    pen2.setWidth(2);

    QPainterPath path;
    path.addRect(QRect(15, 15, 50, 50));
    QTest::newRow("simple rect 0") << QSize(100, 100) << path
                                   << QRect(15, 15, 49, 49)
                                   << QBrush(Qt::NoBrush)
                                   << QPen(Qt::black);
    QTest::newRow("simple rect 1") << QSize(100, 100) << path
                                   << QRect(15, 15, 50, 50)
                                   << QBrush(Qt::NoBrush)
                                   << QPen(Qt::black);
    QTest::newRow("simple rect 2") << QSize(100, 100) << path
                                   << QRect(15, 15, 51, 51)
                                   << QBrush(Qt::NoBrush)
                                   << QPen(Qt::black);
    QTest::newRow("simple rect 3") << QSize(100, 100) << path
                                   << QRect(15, 15, 51, 51)
                                   << QBrush(QColor(Qt::blue))
                                   << QPen(Qt::NoPen);
    QTest::newRow("simple rect 4") << QSize(100, 100) << path
                                   << QRect(15, 15, 51, 51)
                                   << QBrush(gradient)
                                   << pen2;

    path = QPainterPath();
    path.addEllipse(QRect(15, 15, 50, 50));
    QTest::newRow("ellipse 0") << QSize(100, 100) << path
                               << QRect(15, 15, 49, 49)
                               << QBrush(Qt::NoBrush)
                               << QPen(Qt::black);
    QTest::newRow("ellipse 1") << QSize(100, 100) << path
                               << QRect(15, 15, 50, 50)
                               << QBrush(Qt::NoBrush)
                               << QPen(Qt::black);
    QTest::newRow("ellipse 2") << QSize(100, 100) << path
                               << QRect(15, 15, 51, 51)
                               << QBrush(Qt::NoBrush)
                               << QPen(Qt::black);
    QTest::newRow("ellipse 3") << QSize(100, 100) << path
                               << QRect(15, 15, 51, 51)
                               << QBrush(QColor(Qt::blue))
                               << QPen(Qt::NoPen);
    QTest::newRow("ellipse 4") << QSize(100, 100) << path
                               << QRect(15, 15, 51, 51)
                               << QBrush(gradient)
                               << pen2;

    path = QPainterPath();
    path.addRoundedRect(QRect(15, 15, 50, 50), 20, Qt::RelativeSize);
    QTest::newRow("round rect 0") << QSize(100, 100) << path
                                  << QRect(15, 15, 49, 49)
                                  << QBrush(Qt::NoBrush)
                                  << QPen(Qt::black);
    QTest::newRow("round rect 1") << QSize(100, 100) << path
                                  << QRect(15, 15, 50, 50)
                                  << QBrush(Qt::NoBrush)
                                  << QPen(Qt::black);
    QTest::newRow("round rect 2") << QSize(100, 100) << path
                                  << QRect(15, 15, 51, 51)
                                  << QBrush(Qt::NoBrush)
                                  << QPen(Qt::black);
    QTest::newRow("round rect 3") << QSize(100, 100) << path
                                  << QRect(15, 15, 51, 51)
                                  << QBrush(QColor(Qt::blue))
                                  << QPen(Qt::NoPen);
    QTest::newRow("round rect 4") << QSize(100, 100) << path
                                  << QRect(15, 15, 51, 51)
                                  << QBrush(gradient)
                                  << pen2;

    path = QPainterPath();
    path.moveTo(15, 50);
    path.cubicTo(40, 50, 40, 15, 65, 50);
    path.lineTo(15, 50);
    QTest::newRow("cubic 0") << QSize(100, 100) << path
                             << QRect(15, 15, 49, 49)
                             << QBrush(Qt::NoBrush)
                             << QPen(Qt::black);
    QTest::newRow("cubic 1") << QSize(100, 100) << path
                             << QRect(15, 15, 50, 50)
                             << QBrush(Qt::NoBrush)
                             << QPen(Qt::black);
    QTest::newRow("cubic 2") << QSize(100, 100) << path
                             << QRect(15, 15, 51, 51)
                             << QBrush(Qt::NoBrush)
                             << QPen(Qt::black);
    QTest::newRow("cubic 3") << QSize(100, 100) << path
                             << QRect(15, 15, 51, 51)
                             << QBrush(QColor(Qt::blue))
                             << QPen(Qt::NoPen);
    QTest::newRow("cubic 4") << QSize(100, 100) << path
                             << QRect(15, 15, 51, 51)
                             << QBrush(gradient)
                             << pen2;
}

void tst_QPainter::clippedFillPath()
{
    QFETCH(QSize, imageSize);
    QFETCH(QPainterPath, path);
    QFETCH(QRect, clipRect);
    QPainterPath clipPath;
    clipPath.addRect(clipRect);
    QFETCH(QBrush, brush);
    QFETCH(QPen, pen);

    const int width = imageSize.width();
    const int height = imageSize.height();

    QImage clippedRect(width, height, QImage::Format_ARGB32_Premultiplied);
    clippedRect.fill(0x12345678);
    {
        QPainter painter(&clippedRect);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipRect(clipRect);
        painter.drawPath(path);
    }

    QImage clippedPath(width, height, QImage::Format_ARGB32_Premultiplied);
    clippedPath.fill(0x12345678);
    {
        QPainter painter(&clippedPath);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipPath(clipPath);
        painter.drawPath(path);
    }

    QCOMPARE(clippedRect, clippedPath);

    // repeat with antialiasing

    clippedRect.fill(0x12345678);
    {
        QPainter painter(&clippedRect);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipRect(clipRect);
        painter.drawPath(path);
    }

    clippedPath.fill(0x12345678);
    {
        QPainter painter(&clippedPath);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipPath(clipPath);
        painter.drawPath(path);
    }

    QCOMPARE(clippedRect, clippedPath);
}

void tst_QPainter::clippedLines_data()
{
    QTest::addColumn<QSize>("imageSize");
    QTest::addColumn<QLineF>("line");
    QTest::addColumn<QRect>("clipRect");
    QTest::addColumn<QPen>("pen");

    QPen pen2(QColor(223, 223, 0, 223));
    pen2.setWidth(2);

    const auto lines = {
        QLineF(15, 15, 65, 65),
        QLineF(14, 14, 66, 66),
        QLineF(16, 16, 64, 64),
        QLineF(65, 65, 15, 15),
        QLineF(66, 66, 14, 14),
        QLineF(64, 64, 14, 14),
        QLineF(15, 50, 15, 64),
        QLineF(15, 50, 15, 65),
        QLineF(15, 50, 15, 66),
        QLineF(15, 50, 64, 50),
        QLineF(15, 50, 65, 50),
        QLineF(15, 50, 66, 50),
    };

    for (QLineF line : lines) {
        const QByteArray desc = "line (" + QByteArray::number(line.x1())
            + ", " + QByteArray::number(line.y1()) + ", "
            + QByteArray::number(line.x2()) + ", " + QByteArray::number(line.y2())
            + ") ";
        QTest::newRow((desc + '0').constData()) << QSize(100, 100) << line
                                   << QRect(15, 15, 49, 49)
                                   << QPen(Qt::black);
        QTest::newRow((desc + '1').constData()) << QSize(100, 100) << line
                                   << QRect(15, 15, 50, 50)
                                   << QPen(Qt::black);
        QTest::newRow((desc + '2').constData()) << QSize(100, 100) << line
                                   << QRect(15, 15, 51, 51)
                                   << QPen(Qt::black);
        QTest::newRow((desc + '3').constData()) << QSize(100, 100) << line
                                   << QRect(15, 15, 51, 51)
                                   << QPen(Qt::NoPen);
        QTest::newRow((desc + '4').constData()) << QSize(100, 100) << line
                                   << QRect(15, 15, 51, 51)
                                   << pen2;
    }
}

void tst_QPainter::clippedLines()
{
    QFETCH(QSize, imageSize);
    QFETCH(QLineF, line);
    QFETCH(QRect, clipRect);
    QPainterPath clipPath;
    clipPath.addRect(clipRect);
    QFETCH(QPen, pen);

    const int width = imageSize.width();
    const int height = imageSize.height();

    QImage clippedRect(width, height, QImage::Format_ARGB32_Premultiplied);
    clippedRect.fill(0x12345678);
    {
        QPainter painter(&clippedRect);
        painter.setPen(pen);
        painter.setClipRect(clipRect);
        painter.drawLine(line);
        painter.drawLine(line.toLine());
    }

    QImage clippedPath(width, height, QImage::Format_ARGB32_Premultiplied);
    clippedPath.fill(0x12345678);
    {
        QPainter painter(&clippedPath);
        painter.setPen(pen);
        painter.setClipPath(clipPath);
        painter.drawLine(line);
        painter.drawLine(line.toLine());
    }

    QCOMPARE(clippedRect, clippedPath);

    // repeat with antialiasing
    clippedRect.fill(0x12345678);
    {
        QPainter painter(&clippedRect);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setPen(pen);
        painter.setClipRect(clipRect);
        painter.drawLine(line);
        painter.drawLine(line.toLine());
    }

    clippedPath.fill(0x12345678);
    {
        QPainter painter(&clippedPath);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setPen(pen);
        painter.setClipPath(clipPath);
        painter.drawLine(line);
        painter.drawLine(line.toLine());
    }

    QCOMPARE(clippedRect, clippedPath);
}

void tst_QPainter::clippedPolygon_data()
{
    clippedFillPath_data();
};

void tst_QPainter::clippedPolygon()
{
    QFETCH(QSize, imageSize);
    QFETCH(QPainterPath, path);
    QPolygonF polygon = path.toFillPolygon(QTransform());
    QFETCH(QRect, clipRect);
    QPainterPath clipPath;
    clipPath.addRect(clipRect);
    QFETCH(QPen, pen);
    QFETCH(QBrush, brush);

    const int width = imageSize.width();
    const int height = imageSize.height();

    QImage clippedRect(width, height, QImage::Format_ARGB32_Premultiplied);
    clippedRect.fill(0x12345678);
    {
        QPainter painter(&clippedRect);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipRect(clipRect);
        painter.drawPolygon(polygon);
        painter.drawPolygon(polygon.toPolygon());
    }

    QImage clippedPath(width, height, QImage::Format_ARGB32_Premultiplied);
    clippedPath.fill(0x12345678);
    {
        QPainter painter(&clippedPath);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipRect(clipRect);
        painter.drawPolygon(polygon);
        painter.drawPolygon(polygon.toPolygon());
    }

    QCOMPARE(clippedRect, clippedPath);

    // repeat with antialiasing

    clippedRect.fill(0x12345678);
    {
        QPainter painter(&clippedRect);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipRect(clipRect);
        painter.drawPolygon(polygon);
        painter.drawPolygon(polygon.toPolygon());
    }

    clippedPath.fill(0x12345678);
    {
        QPainter painter(&clippedPath);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setPen(pen);
        painter.setBrush(brush);
        painter.setClipRect(clipRect);
        painter.drawPolygon(polygon);
        painter.drawPolygon(polygon.toPolygon());
    }

    QCOMPARE(clippedRect, clippedPath);
}

// this just draws some text that should be clipped in the raster
// paint engine.
void tst_QPainter::clippedText()
{
    for (char ch = 'A'; ch < 'Z'; ++ch) {
        //qDebug() << ch;
        QFont f;
        f.setPixelSize(24);
        QFontMetrics metrics(f);
        QRect textRect = metrics.boundingRect(QChar(ch));

        if (textRect.width() <= 8)
            continue;
        if (textRect.height() <= 8)
            continue;

        QRect imageRect = textRect.adjusted(4, 4, -4, -4);

        QImage image(imageRect.size(), QImage::Format_ARGB32_Premultiplied);

        image.fill(qRgba(255, 255, 255, 255));
        {
            QPainter painter(&image);
            painter.setFont(f);
            painter.setPen(Qt::black);

            painter.drawText(0, 0, QChar(ch));
        }

        image.fill(qRgba(255, 255, 255, 255));
        {
            QPainter painter(&image);
            painter.setFont(f);
            painter.setPen(Qt::black);

            painter.drawText(-imageRect.topLeft(), QChar(ch));
        }

        bool foundPixel = false;
        for (int x = 0; x < image.width(); ++x)
            for (int y = 0; y < image.height(); ++y)
                if (image.pixel(x, y) != 0)
                    foundPixel = true;
        // can't QVERIFY(foundPixel) as sometimes all pixels are clipped
        // away. For example for 'O'
        // just call /some/ function to prevent the compiler from optimizing
        // foundPixel away
        QString::number(foundPixel);

        //image.save(QString("debug") + ch + ".xpm");
    }

    QVERIFY(true); // reached, don't trigger any valgrind errors
}

void tst_QPainter::setOpacity_data()
{
    QTest::addColumn<QImage::Format>("destFormat");
    QTest::addColumn<QImage::Format>("srcFormat");

    QTest::newRow("ARGB32P on ARGB32P") << QImage::Format_ARGB32_Premultiplied
                                        << QImage::Format_ARGB32_Premultiplied;

    QTest::newRow("ARGB32 on ARGB32") << QImage::Format_ARGB32
                                      << QImage::Format_ARGB32;

    QTest::newRow("RGB32 on RGB32") << QImage::Format_RGB32
                                    << QImage::Format_RGB32;

    QTest::newRow("RGB16 on RGB16") << QImage::Format_RGB16
                                    << QImage::Format_RGB16;

    QTest::newRow("ARGB8565_Premultiplied on ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied
                                                                      << QImage::Format_ARGB8565_Premultiplied;

    QTest::newRow("RGB555 on RGB555") << QImage::Format_RGB555
                                      << QImage::Format_RGB555;

    QTest::newRow("RGB666 on RGB666") << QImage::Format_RGB666
                                      << QImage::Format_RGB666;

    QTest::newRow("ARGB8555_Premultiplied on ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied
                                                                      << QImage::Format_ARGB8555_Premultiplied;

    QTest::newRow("RGB888 on RGB888") << QImage::Format_RGB888
                                      << QImage::Format_RGB888;

    QTest::newRow("RGB32 on RGB16") << QImage::Format_RGB16
                                    << QImage::Format_RGB32;

    QTest::newRow("RGB32 on ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied
                                                     << QImage::Format_RGB32;

    QTest::newRow("RGB32 on RGB666") << QImage::Format_RGB666
                                     << QImage::Format_RGB32;

    QTest::newRow("RGB32 on RGB555") << QImage::Format_RGB555
                                     << QImage::Format_RGB32;

    QTest::newRow("RGB32 on ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied
                                                     << QImage::Format_RGB32;

    QTest::newRow("RGB32 on RGB888") << QImage::Format_RGB888
                                     << QImage::Format_RGB32;

    QTest::newRow("RGB16 on RGB32") << QImage::Format_RGB32
                                    << QImage::Format_RGB16;

    QTest::newRow("ARGB8565_Premultiplied on RGB32") << QImage::Format_RGB32
                                                     << QImage::Format_ARGB8565_Premultiplied;

    QTest::newRow("RGB666 on RGB32") << QImage::Format_RGB32
                                     << QImage::Format_RGB666;

    QTest::newRow("RGB555 on RGB32") << QImage::Format_RGB32
                                     << QImage::Format_RGB555;

    QTest::newRow("ARGB8555_Premultiplied on RGB32") << QImage::Format_RGB32
                                                     << QImage::Format_ARGB8555_Premultiplied;

    QTest::newRow("RGB888 on RGB32") << QImage::Format_RGB32
                                     << QImage::Format_RGB888;

    QTest::newRow("RGB555 on RGB888") << QImage::Format_RGB888
                                      << QImage::Format_RGB555;

    QTest::newRow("RGB666 on RGB888") << QImage::Format_RGB888
                                      << QImage::Format_RGB666;

    QTest::newRow("RGB444 on RGB444") << QImage::Format_RGB444
                                      << QImage::Format_RGB444;

    QTest::newRow("RGBA8888P on RGBA8888P") << QImage::Format_RGBA8888_Premultiplied
                                            << QImage::Format_RGBA8888_Premultiplied;

    QTest::newRow("RGBA8888 on RGBA8888") << QImage::Format_RGBA8888
                                          << QImage::Format_RGBA8888;

    QTest::newRow("RGBx8888 on RGBx8888") << QImage::Format_RGBX8888
                                          << QImage::Format_RGBX8888;

    QTest::newRow("RGBA8888P on ARGB32P") << QImage::Format_ARGB32_Premultiplied
                                          << QImage::Format_RGBA8888_Premultiplied;

    QTest::newRow("RGBx8888 on ARGB32P") << QImage::Format_ARGB32_Premultiplied
                                         << QImage::Format_RGBX8888;

    QTest::newRow("ARGB32P on RGBA8888P") << QImage::Format_RGBA8888_Premultiplied
                                          << QImage::Format_ARGB32_Premultiplied;

    QTest::newRow("RGB32 on RGBx8888") << QImage::Format_RGBX8888
                                       << QImage::Format_RGB32;

    QTest::newRow("RGB30 on RGB32") << QImage::Format_RGB32
                                    << QImage::Format_BGR30;

    QTest::newRow("BGR30 on ARGB32P") << QImage::Format_ARGB32_Premultiplied
                                      << QImage::Format_BGR30;

    QTest::newRow("A2RGB30P on ARGB32P") << QImage::Format_ARGB32_Premultiplied
                                         << QImage::Format_A2BGR30_Premultiplied;

    QTest::newRow("A2RGB30P on A2RGB30P") << QImage::Format_A2RGB30_Premultiplied
                                          << QImage::Format_A2RGB30_Premultiplied;

    QTest::newRow("ARGB32P on A2RGB30P") << QImage::Format_A2RGB30_Premultiplied
                                         << QImage::Format_ARGB32_Premultiplied;

    QTest::newRow("RGB32 on A2BGR30P") << QImage::Format_A2BGR30_Premultiplied
                                       << QImage::Format_RGB32;

    QTest::newRow("RGB30 on A2BGR30P") << QImage::Format_A2BGR30_Premultiplied
                                       << QImage::Format_RGB30;

    QTest::newRow("A2RGB30P on A2BGR30P") << QImage::Format_A2BGR30_Premultiplied
                                          << QImage::Format_A2RGB30_Premultiplied;

    QTest::newRow("ARGB32P on BGR30") << QImage::Format_BGR30
                                      << QImage::Format_ARGB32_Premultiplied;

    QTest::newRow("ARGB32P on RGB30") << QImage::Format_RGB30
                                      << QImage::Format_ARGB32_Premultiplied;

    QTest::newRow("A2RGB30P on RGB30") << QImage::Format_RGB30
                                       << QImage::Format_A2RGB30_Premultiplied;

    QTest::newRow("RGBA64P on RGBA64P") << QImage::Format_RGBA64_Premultiplied
                                        << QImage::Format_RGBA64_Premultiplied;

    QTest::newRow("RGBA64 on RGBA64") << QImage::Format_RGBA64
                                      << QImage::Format_RGBA64;

    QTest::newRow("RGBx64 on RGBx64") << QImage::Format_RGBX64
                                      << QImage::Format_RGBX64;

    QTest::newRow("RGBA64P on ARGB32P") << QImage::Format_ARGB32_Premultiplied
                                        << QImage::Format_RGBA64_Premultiplied;

    QTest::newRow("RGBx64 on ARGB32P") << QImage::Format_ARGB32_Premultiplied
                                       << QImage::Format_RGBX64;

    QTest::newRow("ARGB32P on RGBA64P") << QImage::Format_RGBA64_Premultiplied
                                        << QImage::Format_ARGB32_Premultiplied;

}

void tst_QPainter::setOpacity()
{
    QFETCH(QImage::Format, destFormat);
    QFETCH(QImage::Format, srcFormat);

    const QSize imageSize(12, 12);
    const QRect imageRect(QPoint(0, 0), imageSize);
    QColor destColor = Qt::black;
    QColor srcColor = Qt::white;

    QImage dest(imageSize, destFormat);
    QImage src(imageSize, srcFormat);

    QPainter p;
    p.begin(&dest);
    p.fillRect(imageRect, destColor);
    p.end();

    p.begin(&src);
    p.fillRect(imageRect, srcColor);
    p.end();

    p.begin(&dest);
    p.setOpacity(0.5);
    p.drawImage(imageRect, src, imageRect);
    p.end();

    QImage actual = dest.convertToFormat(QImage::Format_RGB32);

    for (int y = 0; y < actual.height(); ++y) {
        QRgb *p = (QRgb *)actual.scanLine(y);
        for (int x = 0; x < actual.width(); ++x) {
            QVERIFY(qAbs(qRed(p[x]) - 127) <= 0xf);
            QVERIFY(qAbs(qGreen(p[x]) - 127) <= 0xf);
            QVERIFY(qAbs(qBlue(p[x]) - 127) <= 0xf);
        }
    }
}

void tst_QPainter::drawhelper_blend_untransformed_data()
{
    setOpacity_data();
}

static const auto &defaultOpacities()
{
    static const std::array opacities = {qreal(0.0), 0.1 , 0.01, 0.4, 0.5, 0.6, 0.9, 1.0};
    return opacities;
}

void tst_QPainter::drawhelper_blend_untransformed()
{
    QFETCH(QImage::Format, destFormat);
    QFETCH(QImage::Format, srcFormat);

    const int size = 128;
    const QSize imageSize(size, size);
    const QRect paintRect(1, 0, size - 2, size); // needs alignment and tailing

    QColor destColor(127, 127, 127);
    QColor srcColor(Qt::white);

    QImage dest(imageSize, destFormat);
    QImage src(imageSize, srcFormat);

    QPainter p;
    p.begin(&src);
    p.fillRect(paintRect, srcColor);
    p.end();

    for (qreal opacity : defaultOpacities()) {
        p.begin(&dest);
        p.fillRect(paintRect, destColor);

        p.setOpacity(opacity);
        p.drawImage(paintRect, src, paintRect);
        p.end();

        // sanity check: make sure all pixels are equal
        QImage expected(size - 2, size, destFormat);
        p.begin(&expected);
        p.fillRect(0, 0, expected.width(), expected.height(),
                   dest.pixelColor(1, 0));
        p.end();

        const QImage subDest(dest.bits() + dest.depth() / 8,
                             dest.width() - 2, dest.height(),
                             dest.bytesPerLine(), dest.format());

        if (dest.format() == QImage::Format_ARGB8565_Premultiplied ||
            dest.format() == QImage::Format_ARGB8555_Premultiplied) {
            // Test skipped due to rounding errors...
            continue;
        }
        QCOMPARE(subDest, expected);
    }
}

void tst_QPainter::drawhelper_blend_tiled_untransformed_data()
{
    setOpacity_data();
}

void tst_QPainter::drawhelper_blend_tiled_untransformed()
{
    QFETCH(QImage::Format, destFormat);
    QFETCH(QImage::Format, srcFormat);

    const int size = 128;
    const QSize imageSize(size, size);
    const QRect paintRect(1, 0, size - 2, size); // needs alignment and tailing

    QColor destColor(127, 127, 127);
    QColor srcColor(Qt::white);

    QImage dest(imageSize, destFormat);
    QImage src(imageSize / 2, srcFormat);

    QPainter p;
    p.begin(&src);
    p.fillRect(QRect(QPoint(0, 0), imageSize/ 2), srcColor);
    p.end();

    const QBrush brush(src);

    for (qreal opacity : defaultOpacities()) {
        p.begin(&dest);
        p.fillRect(paintRect, destColor);

        p.setOpacity(opacity);
        p.fillRect(paintRect, brush);
        p.end();

        // sanity check: make sure all pixels are equal
        QImage expected(size - 2, size, destFormat);
        p.begin(&expected);
        p.fillRect(0, 0, expected.width(), expected.height(),
                   dest.pixelColor(1, 0));
        p.end();

        const QImage subDest(dest.bits() + dest.depth() / 8,
                             dest.width() - 2, dest.height(),
                             dest.bytesPerLine(), dest.format());

        if (dest.format() == QImage::Format_ARGB8565_Premultiplied ||
            dest.format() == QImage::Format_ARGB8555_Premultiplied) {
            // Skipping test due to rounding errors. Test needs rewrite
            continue;
        }
        QCOMPARE(subDest, expected);
    }
}

static QPaintEngine::PaintEngineFeatures no_porter_duff()
{
    QPaintEngine::PaintEngineFeatures features = QPaintEngine::AllFeatures;
    return features & ~QPaintEngine::PorterDuff;
}

class DummyPaintEngine : public QPaintEngine, public QPaintDevice
{
public:
    DummyPaintEngine() : QPaintEngine(no_porter_duff()) {}
    virtual bool begin(QPaintDevice *) override { return true; }
    virtual bool end() override { return true; }

    virtual void updateState(const QPaintEngineState &) override {}
    virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) override {}

    virtual Type type() const override { return User; }

    virtual QPaintEngine *paintEngine() const override { return (QPaintEngine *)this; }

    virtual int metric(PaintDeviceMetric metric) const override { Q_UNUSED(metric); return 0; };
};

static bool success;

void porterDuff_warningChecker(QtMsgType type, const QMessageLogContext &, const QString &msg)
{
    if (type == QtWarningMsg && msg == QLatin1String("QPainter::setCompositionMode: PorterDuff modes not supported on device"))
        success = false;
}

void tst_QPainter::porterDuff_warning()
{
    QtMessageHandler old = qInstallMessageHandler(porterDuff_warningChecker);
    DummyPaintEngine dummy;
    QPainter p(&dummy);

    success = true;
    p.setCompositionMode(QPainter::CompositionMode_Source);
    QVERIFY(success);

    success = true;
    p.setCompositionMode(QPainter::CompositionMode_SourceOver);
    QVERIFY(success);

    success = true;
    p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
    QVERIFY(!success);

    QVERIFY(qInstallMessageHandler(old) == porterDuff_warningChecker);
}

void tst_QPainter::drawhelper_blend_color()
{
    QImage dest(32, 32, QImage::Format_ARGB8555_Premultiplied);
    dest.fill(0xff000000);

    {
        QPainter p(&dest);
        p.fillRect(0, 0, dest.width(), dest.height(), QColor(255, 0, 0, 127));
    }

    QImage expected(32, 32, QImage::Format_ARGB8555_Premultiplied);
    expected.fill(0xff3c007f);

    QCOMPARE(dest.pixel(1, 1), expected.pixel(1, 1));
    QCOMPARE(dest, expected);
}

#ifndef QT_NO_WIDGETS
class ViewportTestWidget : public QWidget
{
public:
    ViewportTestWidget(QWidget *parent = nullptr) : QWidget(parent), hasPainted(false) {}
    QSize sizeHint() const override
    {
        return QSize(100, 100);
    }

    QRect viewport;
    bool hasPainted;

protected:
    void paintEvent(QPaintEvent *) override
    {
        hasPainted = true;
        QPainter p(this);
        viewport = p.viewport();
    }
};

void tst_QPainter::childWidgetViewport()
{
    QWidget parent;
    parent.setAutoFillBackground(true);
    parent.resize(200, 200);
    ViewportTestWidget child(&parent);
    child.setAutoFillBackground(true);
    parent.show();
    parent.update();
    qApp->processEvents();

    if (child.hasPainted) {
        QCOMPARE(child.viewport, QRect(QPoint(0, 0), child.sizeHint()));
    } else {
        qWarning("Failed to ensure that paintEvent has been run. Could not run test.");
    }
}
#endif

void tst_QPainter::fillRect_objectBoundingModeGradient()
{
    QImage a(10, 10, QImage::Format_ARGB32_Premultiplied);
    a.fill(0x0);
    QImage b = a;

    QLinearGradient g(QPoint(0, 0), QPoint(0, 1));
    g.setColorAt(0, Qt::red);
    g.setColorAt(1, Qt::blue);
    g.setCoordinateMode(QGradient::ObjectBoundingMode);

    QPainter p(&a);
    p.fillRect(QRect(0, 0, a.width(), a.height()), g);
    p.end();

    QPainterPath path;
    path.addRect(0, 0, a.width(), a.height());

    p.begin(&b);
    p.fillPath(path, g);
    p.end();

    QCOMPARE(a, b);
}

void tst_QPainter::fillRect_stretchToDeviceMode()
{
    QImage img(64, 64, QImage::Format_ARGB32_Premultiplied);

    QLinearGradient g(QPoint(0, 0), QPoint(0, 1));
    g.setCoordinateMode(QGradient::StretchToDeviceMode);

    QPainter p(&img);
    p.fillRect(img.rect(), g);
    p.end();

    for (int i = 1; i < img.height(); ++i)
        QVERIFY(img.pixel(0, i) != img.pixel(0, i-1));
}

void tst_QPainter::monoImages()
{
    Qt::GlobalColor colorPairs[][2] = {
        { Qt::white, Qt::black },
        { Qt::color0, Qt::color1 },
        { Qt::red, Qt::blue }
    };

    const int numColorPairs = sizeof(colorPairs) / sizeof(QRgb[2]);

    QImage transparent(2, 2, QImage::Format_ARGB32_Premultiplied);
    transparent.fill(0x0);

    for (int i = 1; i < QImage::NImageFormats; ++i) {
        for (int j = 0; j < numColorPairs; ++j) {
            const QImage::Format format = QImage::Format(i);
            if (format == QImage::Format_Indexed8 || format == QImage::Format_CMYK8888)
                continue;

            QImage img(2, 2, format);

            if (img.colorCount() > 0) {
                img.setColor(0, QColor(colorPairs[j][0]).rgba());
                img.setColor(1, QColor(colorPairs[j][1]).rgba());
            }

            img.fill(0x0);
            QPainter p(&img);
            p.fillRect(0, 0, 2, 2, colorPairs[j][0]);
            p.fillRect(0, 0, 1, 1, colorPairs[j][1]);
            p.fillRect(1, 1, 1, 1, colorPairs[j][1]);
            p.end();

            QImage original = img;

            p.begin(&img);
            p.drawImage(0, 0, transparent);
            p.end();

            // drawing a transparent image on top of another image
            // should not change the image
            QCOMPARE(original, img);

            if (img.colorCount() == 0)
                continue;

            for (int k = 0; k < 2; ++k) {
                QPainter p(&img);
                p.fillRect(0, 0, 2, 2, colorPairs[j][k]);
                p.end();

                QImage argb32p(2, 2, QImage::Format_ARGB32_Premultiplied);
                p.begin(&argb32p);
                p.fillRect(0, 0, 2, 2, colorPairs[j][k]);
                p.end();

                QCOMPARE(argb32p, img.convertToFormat(argb32p.format()));

                // drawing argb32p image on mono image
                p.begin(&img);
                p.drawImage(0, 0, argb32p);
                p.end();

                QCOMPARE(argb32p, img.convertToFormat(argb32p.format()));

                // drawing mono image on argb32p image
                p.begin(&argb32p);
                p.drawImage(0, 0, img);
                p.end();

                QCOMPARE(argb32p, img.convertToFormat(argb32p.format()));
            }
        }
    }
}

#if defined(Q_OS_DARWIN) || defined(Q_OS_FREEBSD) || defined(Q_OS_ANDROID)
#  define TEST_FPE_EXCEPTIONS
#elif defined(__GLIBC__)
#  define TEST_FPE_EXCEPTIONS
#elif defined(Q_OS_WIN) && defined(Q_CC_GNU)
#  define TEST_FPE_EXCEPTIONS
#endif
#ifdef TEST_FPE_EXCEPTIONS
#include <fenv.h>

static const QString fpeExceptionString(int exception)
{
#ifdef FE_INEXACT
    if (exception & FE_INEXACT)
        return QLatin1String("Inexact result");
#endif
    if (exception & FE_UNDERFLOW)
        return QLatin1String("Underflow");
    if (exception & FE_OVERFLOW)
        return QLatin1String("Overflow");
    if (exception & FE_DIVBYZERO)
        return QLatin1String("Divide by zero");
    if (exception & FE_INVALID)
        return QLatin1String("Invalid operation");
    return QLatin1String("No exception");
}

class FpExceptionChecker
{
public:
    FpExceptionChecker(int exceptionMask)
        : m_exceptionMask(exceptionMask)
    {
        feclearexcept(m_exceptionMask);
    }

    ~FpExceptionChecker()
    {
        const int exceptions = fetestexcept(m_exceptionMask);
        QVERIFY2(!exceptions, qPrintable(QLatin1String("Floating point exception: ") + fpeExceptionString(exceptions)));
    }

private:
    int m_exceptionMask;
};

void fpe_rasterizeLine_task232012()
{
    FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO);
    QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);
    QPainter p(&img);

    p.setBrush(Qt::black);
    p.drawRect(QRectF(0, 0, 5, 0));
    p.drawRect(QRectF(0, 0, 0, 5));
}

void fpe_pixmapTransform()
{
    FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO);

    QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);

    QPainter p(&img);

    const qreal scaleFactor = 0.001;
    const int translateDistance = 1000000;

    p.setPen(Qt::red);
    p.setBrush(QBrush(Qt::red,Qt::Dense6Pattern));

    for (int i = 0; i < 2; ++i) {
        p.setRenderHint(QPainter::SmoothPixmapTransform, i);

        p.resetTransform();
        p.scale(1.1, 1.1);
        p.translate(translateDistance, 0);
        p.drawRect(-translateDistance, 0, 100, 100);

        p.resetTransform();
        p.scale(scaleFactor, scaleFactor);
        p.drawRect(QRectF(0, 0, 1 / scaleFactor, 1 / scaleFactor));
    }
}

void fpe_zeroLengthLines()
{
    FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO);

    QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);

    QPainter p(&img);

    p.setPen(QPen(Qt::black, 3));
    p.drawLine(64, 64, 64, 64);
}

void fpe_divByZero()
{
    FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO);

    QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);

    QPainter p(&img);

    p.setRenderHint(QPainter::Antialiasing);

    p.drawRect(QRectF(10, 10, 100, 0));
    p.drawRect(QRectF(10, 10, 0, 100));

    p.drawRect(QRect(10, 10, 100, 0));
    p.drawRect(QRect(10, 10, 0, 100));

    p.fillRect(QRectF(10, 10, 100, 0), Qt::black);
    p.fillRect(QRectF(10, 10, 0, 100), Qt::black);

    p.fillRect(QRect(10, 10, 100, 0), Qt::black);
    p.fillRect(QRect(10, 10, 0, 100), Qt::black);
}

void fpe_steepSlopes()
{
    FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO);

    QImage img(1024, 1024, QImage::Format_ARGB32_Premultiplied);

    QFETCH(QTransform, transform);
    QFETCH(QLineF, line);
    QFETCH(bool, antialiased);

    QPainter p(&img);

    p.setPen(QPen(Qt::black, 1));
    p.setRenderHint(QPainter::Antialiasing, antialiased);
    p.setTransform(transform);

    p.drawLine(line);
}

void fpe_radialGradients()
{
    FpExceptionChecker checker(FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID | FE_DIVBYZERO);

    QImage img(21, 21, QImage::Format_ARGB32_Premultiplied);
    img.fill(0);

    double m = img.width() * 0.5;

    QPainter p(&img);
    p.setRenderHints(QPainter::Antialiasing);
    p.setPen(Qt::NoPen);
    p.setBrush(QRadialGradient(m, m, m));
    p.drawEllipse(img.rect());
}

#define FPE_TEST(x) \
void tst_QPainter::x() \
{ \
    ::x(); \
}
#else
#define FPE_TEST(x) \
void tst_QPainter::x() \
{ \
    QSKIP("Floating point exception checking (fenv.h) not available"); \
}
#endif

FPE_TEST(fpe_rasterizeLine_task232012)
FPE_TEST(fpe_pixmapTransform)
FPE_TEST(fpe_zeroLengthLines)
FPE_TEST(fpe_divByZero)
FPE_TEST(fpe_steepSlopes)
FPE_TEST(fpe_radialGradients)

void tst_QPainter::fpe_steepSlopes_data()
{
    QTest::addColumn<QTransform>("transform");
    QTest::addColumn<QLineF>("line");
    QTest::addColumn<bool>("antialiased");

    {
        const qreal dsin = 0.000014946676875461832484392500630665523431162000633776187896728515625;
        const qreal dcos = 0.9999999998882984630910186751862056553363800048828125;

        const QTransform transform = QTransform(dcos, dsin, -dsin, dcos, 64, 64);
        const QLineF line(2, 2, 2, 6);

        QTest::newRow("task 207147 aa") << transform << line << true;
        QTest::newRow("task 207147 no aa") << transform << line << false;
    }

    {
        QTransform transform;
        transform.rotate(0.0000001);
        const QLineF line(5, 5, 10, 5);

        QTest::newRow("task 166702 aa") << transform << line << true;
        QTest::newRow("task 166702 no aa") << transform << line << false;
    }

    {
        const QTransform transform;
        const QLineF line(2.5, 2.5, 2.5 + 1/256., 60000.5);

        QTest::newRow("steep line aa") << transform << line << true;
        QTest::newRow("steep line no aa") << transform << line << false;
    }

    {
        const QTransform transform;
        const QLineF line(2.5, 2.5, 2.5 + 1/256., 1024);

        QTest::newRow("steep line 2 aa") << transform << line << true;
        QTest::newRow("steep line 2 no aa") << transform << line << false;
    }

    {
        const QTransform transform;
        const QLineF line(2.5, 2.5, 2.5 + 1/64., 1024);

        QTest::newRow("steep line 3 aa") << transform << line << true;
        QTest::newRow("steep line 3 no aa") << transform << line << false;
    }
}

qreal randf()
{
    return QRandomGenerator::global()->bounded(1.0);
}

QPointF randInRect(const QRectF &rect)
{
    const qreal x = rect.left() + rect.width() * randf();
    const qreal y = rect.top() + rect.height() * randf();

    return QPointF(x, y);
}

void tst_QPainter::rasterizer_asserts()
{
    QImage img(64, 64, QImage::Format_ARGB32_Premultiplied);

    QRectF middle(QPointF(0, 0), img.size());
    QRectF left = middle.translated(-middle.width(), 0);
    QRectF right = middle.translated(middle.width(), 0);

    QPainter p(&img);
    img.fill(Qt::white);
    p.setCompositionMode(QPainter::CompositionMode_Destination);
    for (int i = 0; i < 100000; ++i) {
        QPainterPath path;
        path.moveTo(randInRect(middle));
        path.lineTo(randInRect(left));
        path.lineTo(randInRect(right));

        p.fillPath(path, Qt::black);
    }
}

void tst_QPainter::rasterizer_negativeCoords()
{
    QImage img(64, 64, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);

    QImage original = img;

    QPainter p(&img);
    p.rotate(90);
    p.fillRect(0, 0, 70, 50, Qt::black);

    // image should not have changed
    QCOMPARE(img.pixel(0, 0), 0x0U);
    QCOMPARE(img, original);
}

void tst_QPainter::blendOverFlow_data()
{
    QTest::addColumn<QImage::Format>("format");
    QTest::addColumn<int>("width");
    QTest::addColumn<int>("height");

    QImage::Format format = QImage::Format_ARGB8555_Premultiplied;
    QTest::newRow("555,1,1") << format << 1 << 1;
    QTest::newRow("555,2,2") << format << 2 << 2;
    QTest::newRow("555,10,10") << format << 10 << 10;

    format = QImage::Format_ARGB8565_Premultiplied;
    QTest::newRow("565,1,1") << format << 1 << 1;
    QTest::newRow("565,2,2") << format << 2 << 2;
    QTest::newRow("565,10,10") << format << 10 << 10;
}

void tst_QPainter::blendOverFlow()
{
    QFETCH(QImage::Format, format);
    QFETCH(int, width);
    QFETCH(int, height);

    QImage dest(width, height, format);
    QImage src(width, height, format);

    {
        QPainter p(&dest);
        p.fillRect(0, 0, width, height, Qt::green);
    }
    QImage expected = dest;

    {
        QPainter p(&src);
        p.setCompositionMode(QPainter::CompositionMode_Source);
        p.fillRect(0, 0, width, height, QColor(0, 255, 0, 6));
    }

    {
        QPainter p(&dest);
        p.drawImage(0, 0, src);
    }

    QCOMPARE(dest.pixel(0, 0), expected.pixel(0, 0));
    QCOMPARE(dest, expected);
}

void tst_QPainter::largeImagePainting_data()
{
    QTest::addColumn<int>("width");
    QTest::addColumn<int>("height");
    QTest::addColumn<bool>("antialiased");

    QTest::newRow("tall") << 1 << 32767 << false;
    QTest::newRow("tall aa") << 1 << 32767 << true;
    QTest::newRow("wide") << 32767 << 1 << false;
    QTest::newRow("wide aa") << 32767 << 1 << true;
}

void tst_QPainter::largeImagePainting()
{
    QPainterPath path;
    path.addRect(0, 0, 1, 1);
    path.addRect(2, 0, 1, 1);
    path.addRect(0, 2, 1, 1);

    QFETCH(int, width);
    QFETCH(int, height);
    QFETCH(bool, antialiased);

    QImage img(width, height, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);

    QPainter p(&img);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::white);

    p.setRenderHint(QPainter::Antialiasing, antialiased);

    for (int i = 0; i < img.width(); i += 4) {
        p.drawPath(path);
        p.translate(4, 0);
    }

    p.resetTransform();

    for (int i = 4; i < img.height(); i += 4) {
        p.translate(0, 4);
        p.drawPath(path);
    }

    for (int i = 0; i < img.width(); ++i) {
        if (i % 2)
            QCOMPARE(img.pixel(i, 0), 0x0U);
        else
            QCOMPARE(img.pixel(i, 0), 0xffffffffU);
    }

    for (int i = 1; i < img.height(); ++i) {
        if (i % 2)
            QCOMPARE(img.pixel(0, i), 0x0U);
        else
            QCOMPARE(img.pixel(0, i), 0xffffffffU);
    }
}

void tst_QPainter::imageScaling_task206785()
{
    QImage src(32, 2, QImage::Format_ARGB32_Premultiplied);
    src.fill(0xffffffff);

    QImage dst(128, 128, QImage::Format_ARGB32_Premultiplied);

    QImage expected(128, 128, QImage::Format_ARGB32_Premultiplied);
    expected.fill(0xffffffff);

    for (int i = 1; i < 5; ++i) {
        qreal scale = i / qreal(5);

        dst.fill(0xff000000);

        QPainter p(&dst);
        p.scale(dst.width() / qreal(src.width()), scale);

        for (int y = 0; y * scale < dst.height(); ++y)
            p.drawImage(0, y, src);

        p.end();

        QCOMPARE(dst, expected);
    }
}

#define FOR_EACH_NEIGHBOR_8 for (int dx = -1; dx <= 1; ++dx) for (int dy = -1; dy <= 1; ++dy) if (dx != 0 || dy != 0)
#define FOR_EACH_NEIGHBOR_4 for (int dx = -1; dx <= 1; ++dx) for (int dy = -1; dy <= 1; ++dy) if ((dx == 0) != (dy == 0))

bool verifyOutlineFillConsistency(const QImage &img, QRgb outside, QRgb inside, QRgb outline)
{
    if (img.pixel(img.width() / 2, img.height() / 2) != inside)
        return false;

    int x = img.width() / 2;
    int y = img.height() / 2;

    while (img.pixel(++x, y) == inside)
        ;

    if (img.pixel(x, y) != outline)
        return false;

    QQueue<QPoint> discovered;
    discovered.enqueue(QPoint(x, y));

    QList<bool> visited(img.width() * img.height());
    visited.fill(false);

    while (!discovered.isEmpty()) {
        QPoint p = discovered.dequeue();
        QRgb pixel = img.pixel(p.x(), p.y());

        bool &v = visited[p.y() * img.width() + p.x()];
        if (v)
            continue;
        v = true;

        if (pixel == outline) {
            FOR_EACH_NEIGHBOR_8 {
                QPoint x(p.x() + dx, p.y() + dy);
                discovered.enqueue(x);
            }
        } else {
            FOR_EACH_NEIGHBOR_4 {
                if ((dx == 0) == (dy == 0))
                    continue;
                QRgb neighbor = img.pixel(p.x() + dx, p.y() + dy);
                if ((pixel == inside && neighbor == outside) ||
                    (pixel == outside && neighbor == inside))
                    return false;
            }
        }
    }

    return true;
}

#undef FOR_EACH_NEIGHBOR_8
#undef FOR_EACH_NEIGHBOR_4

void tst_QPainter::outlineFillConsistency()
{
    QImage dst(256, 256, QImage::Format_ARGB32_Premultiplied);

    QPolygonF poly;
    poly << QPointF(5, -100) << QPointF(-70, 20) << QPointF(95, 25);

    QPen pen(Qt::red);
    QBrush brush(Qt::black);

    QRgb background = 0xffffffff;
    for (int i = 0; i < 360; ++i) {
        dst.fill(background);

        QPainter p(&dst);
        p.translate(dst.width() / 2, dst.height() / 2);

        QPolygonF copy = poly;
        for (int j = 0; j < copy.size(); ++j)
            copy[j] = QTransform().rotate(i).map(copy[j]);

        p.setPen(pen);
        p.setBrush(brush);
        p.drawPolygon(copy);
        p.end();

        QVERIFY(verifyOutlineFillConsistency(dst, background, brush.color().rgba(), pen.color().rgba()));
    }
}

void tst_QPainter::drawImage_task217400_data()
{
    QTest::addColumn<QImage::Format>("format");

    QTest::newRow("444") << QImage::Format_ARGB4444_Premultiplied;
    QTest::newRow("555") << QImage::Format_ARGB8555_Premultiplied;
    QTest::newRow("565") << QImage::Format_ARGB8565_Premultiplied;
//    QTest::newRow("666") << QImage::Format_ARGB6666_Premultiplied;
    QTest::newRow("888p") << QImage::Format_ARGB32_Premultiplied;
    QTest::newRow("888") << QImage::Format_ARGB32;
}

void tst_QPainter::drawImage_task217400()
{
    QFETCH(QImage::Format, format);

    const QImage src = QImage(QFINDTESTDATA("task217400.png"))
                       .convertToFormat(format);
    QVERIFY(!src.isNull());

    QImage expected(src.size(), format);
    {
        QPainter p(&expected);
        p.fillRect(0, 0, expected.width(), expected.height(), Qt::white);
        p.drawImage(0, 0, src);
    }

    for (int i = 1; i <= 4; ++i) {
        QImage dest(src.width() + i, src.height(), format);
        {
            QPainter p(&dest);
            p.fillRect(0, 0, dest.width(), dest.height(), Qt::white);
            p.drawImage(i, 0, src);
        }

        const QImage result = dest.copy(i, 0, src.width(), src.height());

        QCOMPARE(result, expected);
    }
}

void tst_QPainter::drawImage_task258776()
{
    QImage src(16, 16, QImage::Format_RGB888);
    QImage dest(33, 33, QImage::Format_RGB888);
    src.fill(0x00ff00);
    dest.fill(0xff0000);

    QPainter painter(&dest);
    painter.drawImage(QRectF(0.499, 0.499, 32, 32), src, QRectF(0, 0, 16, 16));
    painter.end();

    QImage expected(33, 33, QImage::Format_RGB32);
    expected.fill(0xff0000);

    painter.begin(&expected);
    painter.drawImage(QRectF(0, 0, 32, 32), src);
    painter.end();

    dest = dest.convertToFormat(QImage::Format_RGB32);

    dest.save("dest.png");
    expected.save("expected.png");
    QCOMPARE(dest, expected);
}

void tst_QPainter::drawImage_QTBUG28324()
{
    QImage dest(512, 512, QImage::Format_ARGB32_Premultiplied);
    dest.fill(0x0);

    int x = 263; int y = 89; int w = 61; int h = 39;

    QImage source(w, h, QImage::Format_ARGB32_Premultiplied);
    quint32 *b = (quint32 *)source.bits();
    for (int j = 0; j < w * h; ++j)
        b[j] = 0x7f7f7f7f;

    // nothing to test here since the bug is about
    // an invalid memory read, which valgrind
    // would complain about
    QPainter p(&dest);
    p.drawImage(x, y, source);
}

void tst_QPainter::clipRectSaveRestore()
{
    QImage img(64, 64, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);

    QPainter p(&img);
    p.setClipRect(QRect(0, 0, 10, 10));
    p.save();
    p.setClipRect(QRect(5, 5, 5, 5), Qt::IntersectClip);
    p.restore();
    p.fillRect(0, 0, 64, 64, Qt::black);
    p.end();

    QCOMPARE(img.pixel(0, 0), QColor(Qt::black).rgba());
}

void tst_QPainter::clipStateSaveRestore()
{
    QImage img(16, 16, QImage::Format_RGB32);
    img.fill(Qt::blue);
    {
        QPainter p(&img);
        p.setClipRect(QRect(5, 5, 10, 10));
        p.save();
        p.setClipping(false);
        p.restore();
        p.fillRect(0, 0, 16, 16, Qt::red);
        p.end();
        QCOMPARE(img.pixel(0, 0), QColor(Qt::blue).rgb());
    }

    img.fill(Qt::blue);
    {
        QPainter p(&img);
        p.setClipRect(QRect(5, 5, 10, 10));
        p.setClipping(false);
        p.save();
        p.setClipping(true);
        p.restore();
        p.fillRect(0, 0, 16, 16, Qt::red);
        p.end();
        QCOMPARE(img.pixel(0, 0), QColor(Qt::red).rgb());
    }
}

void tst_QPainter::clippedImage()
{
    QImage img(16, 16, QImage::Format_ARGB32_Premultiplied);
    img.fill(0x0);

    QImage src(16, 16, QImage::Format_RGB32);
    src.fill(QColor(Qt::red).rgba());

    QPainter p(&img);
    p.setClipRect(QRect(1, 1, 14, 14));
    p.drawImage(0, 0, src);
    p.end();

    QCOMPARE(img.pixel(0, 0), 0x0U);
    QCOMPARE(img.pixel(1, 1), src.pixel(1, 1));
}

void tst_QPainter::stateResetBetweenQPainters()
{
    QImage img(16, 16, QImage::Format_ARGB32);

    {
        QPainter p(&img);
        p.setCompositionMode(QPainter::CompositionMode_Source);
        p.fillRect(0, 0, 16, 16, Qt::red);
    }

    {
        QPainter p2(&img);
        p2.fillRect(0, 0, 16, 16, QColor(0, 0, 255, 63));
    }

    img.save("foo.png");

    QVERIFY(img.pixel(0, 0) != qRgba(0, 0, 255, 63));
    QVERIFY(qRed(img.pixel(0, 0)) > 0); // We didn't erase the red channel...
    QVERIFY(qBlue(img.pixel(0, 0)) < 255); // We blended the blue channel
}

void tst_QPainter::drawRect_task215378()
{
    QImage img(11, 11, QImage::Format_ARGB32_Premultiplied);
    img.fill(QColor(Qt::white).rgba());

    QPainter p(&img);
    p.setPen(QColor(127, 127, 127, 127));
    p.drawRect(0, 0, 10, 10);
    p.end();

    QCOMPARE(img.pixel(0, 0), img.pixel(1, 0));
    QCOMPARE(img.pixel(0, 0), img.pixel(0, 1));
    QVERIFY(img.pixel(0, 0) != img.pixel(1, 1));
}

void tst_QPainter::drawRect_task247505()
{
    QImage a(10, 10, QImage::Format_ARGB32_Premultiplied);
    a.fill(0);
    QImage b = a;

    QPainter p(&a);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::black);
    p.drawRect(QRectF(10, 0, -10, 10));
    p.end();
    p.begin(&b);
    p.setPen(Qt::NoPen);
    p.setBrush(Qt::black);
    p.drawRect(QRectF(0, 0, 10, 10));
    p.end();

    QCOMPARE(a, b);
}

void tst_QPainter::drawImage_data()
{
    QTest::addColumn<int>("x");
    QTest::addColumn<int>("y");
    QTest::addColumn<int>("w");
    QTest::addColumn<int>("h");
    QTest::addColumn<QImage::Format>("srcFormat");
    QTest::addColumn<QImage::Format>("dstFormat");

    for (int srcFormat = QImage::Format_Mono; srcFormat < QImage::NImageFormats; ++srcFormat) {
        for (int dstFormat = QImage::Format_Mono; dstFormat < QImage::NImageFormats; ++dstFormat) {
            // Indexed8 and CMYK8888 can't be painted to, and Alpha8 can't hold a color.
            if (dstFormat == QImage::Format_Indexed8 ||
                dstFormat == QImage::Format_CMYK8888 ||
                dstFormat == QImage::Format_Alpha8) {
                continue;
            }

            for (int odd_x = 0; odd_x <= 1; ++odd_x) {
                for (int odd_width = 0; odd_width <= 1; ++odd_width) {
                    QTest::addRow("srcFormat %d, dstFormat %d, odd x: %d, odd width: %d",
                                  srcFormat, dstFormat, odd_x, odd_width)
                        << (10 + odd_x) << 10 << (20 + odd_width) << 20
                        << QImage::Format(srcFormat)
                        << QImage::Format(dstFormat);
                }
            }
        }
    }
}

bool verifyImage(const QImage &img, int x, int y, int w, int h, uint background)
{
    int imgWidth = img.width();
    int imgHeight = img.height();
    for (int i = 0; i < imgHeight; ++i) {
        for (int j = 0; j < imgWidth; ++j) {
            uint pixel = img.pixel(j, i);
            bool outside = j < x || j >= (x + w) || i < y || i >= (y + h);
            if (outside != (pixel == background)) {
                //printf("%d %d, expected %x, got %x, outside: %d\n", x, y, background, pixel, outside);
                return false;
            }
        }
    }

    return true;
}

void tst_QPainter::drawImage()
{
    QFETCH(int, x);
    QFETCH(int, y);
    QFETCH(int, w);
    QFETCH(int, h);
    QFETCH(QImage::Format, srcFormat);
    QFETCH(QImage::Format, dstFormat);

    QImage dst(40, 40, QImage::Format_RGB32);
    dst.fill(0xffffffff);

    dst = dst.convertToFormat(dstFormat);
    uint background = dst.pixel(0, 0);

    QImage src(w, h, QImage::Format_RGB32);
    src.fill(0xff000000);
    src = src.convertToFormat(srcFormat);

    QPainter p(&dst);
    p.drawImage(x, y, src);
    p.end();

    QVERIFY(verifyImage(dst, x, y, w, h, background));
}

void tst_QPainter::imageCoordinateLimit()
{
    QImage img(64, 40000, QImage::Format_MonoLSB);
    QPainter p(&img);
    p.drawText(10, 36000, QLatin1String("foo"));
    p.setPen(QPen(Qt::black, 2));
    p.drawLine(10, 0, 60, 40000);

    p.setRenderHint(QPainter::Antialiasing);
    p.drawLine(10, 0, 60, 40000);
}


void tst_QPainter::imageBlending_data()
{
    QTest::addColumn<QImage::Format>("sourceFormat");
    QTest::addColumn<QImage::Format>("destFormat");
    QTest::addColumn<int>("error");

    int error_rgb565 = ((1<<3) + (1<<2) + (1<<3));
    QTest::newRow("rgb565_on_rgb565") << QImage::Format_RGB16
                                      << QImage::Format_RGB16
                                      << 0;
    QTest::newRow("argb8565_on_rgb565") << QImage::Format_ARGB8565_Premultiplied
                                        << QImage::Format_RGB16
                                        << error_rgb565;

    QTest::newRow("rgb32_on_rgb565") << QImage::Format_RGB32
                                     << QImage::Format_RGB16
                                     << error_rgb565;

    QTest::newRow("argb32pm_on_rgb565") << QImage::Format_ARGB32_Premultiplied
                                        << QImage::Format_RGB16
                                        << error_rgb565;
}

int diffColor(quint32 ap, quint32 bp)
{
    int a = qAlpha(ap) - qAlpha(bp);
    int r = qRed(ap) - qRed(bp);
    int b = qBlue(ap) - qBlue(bp);
    int g = qBlue(ap) - qBlue(bp);

    return qAbs(a) + qAbs(r) + qAbs(g) + qAbs(b);
}

// this test assumes premultiplied pixels...

void tst_QPainter::imageBlending()
{
    QFETCH(QImage::Format, sourceFormat);
    QFETCH(QImage::Format, destFormat);
    QFETCH(int, error);

    QImage dest;
    {
        QImage orig_dest(6, 6, QImage::Format_ARGB32_Premultiplied);
        orig_dest.fill(0);
        QPainter p(&orig_dest);
        p.fillRect(0, 0, 6, 3, QColor::fromRgbF(1, 0, 0));
        p.fillRect(3, 0, 3, 6, QColor::fromRgbF(0, 0, 1, 0.5));
        p.end();
        dest = orig_dest.convertToFormat(destFormat);

        // An image like this: (r = red, m = magenta, b = light alpha blue, 0 = transparent)
        // r r r m m m
        // r r r m m m
        // r r r m m m
        // 0 0 0 b b b
        // 0 0 0 b b b
        // 0 0 0 b b b
    }

    QImage source;
    {
        QImage orig_source(6, 6, QImage::Format_ARGB32_Premultiplied);
        orig_source.fill(0);
        QPainter p(&orig_source);
        p.fillRect(1, 1, 4, 4, QColor::fromRgbF(0, 1, 0, 0.5));
        p.fillRect(2, 2, 2, 2, QColor::fromRgbF(0, 1, 0));
        p.end();
        source = orig_source.convertToFormat(sourceFormat);

        // An image like this: (0 = transparent, . = green at 0.5 alpha, g = opaque green.
        // 0 0 0 0 0 0
        // 0 . . . . 0
        // 0 . g g . 0
        // 0 . g g . 0
        // 0 . . . . 0
        // 0 0 0 0 0 0
    }

    QPainter p(&dest);
    p.drawImage(0, 0, source);
    p.end();

    // resulting image:
    // r  r  r  m  m  m
    // r  r. r. m. m. m
    // r  r. g  g  m. m
    // 0  .  g  g  b. b
    // 0  .  .  b. b. b
    // 0  0  0  b  b  b

    // the g pixels, always green..
    QVERIFY(diffColor(dest.pixel(2, 2), 0xff00ff00) <= error); // g

    if (source.hasAlphaChannel()) {
        QVERIFY(diffColor(dest.pixel(0, 0), 0xffff0000) <= error); // r
        QVERIFY(diffColor(dest.pixel(5, 0), 0xff7f007f) <= error); // m
        QVERIFY(diffColor(dest.pixel(1, 1), 0xff7f7f00) <= error); // r.
        QVERIFY(diffColor(dest.pixel(4, 1), 0xff3f7f3f) <= error); // m.
        if (dest.hasAlphaChannel()) {
            QVERIFY(diffColor(dest.pixel(1, 3), 0x7f007f00) <= error); // .
            QVERIFY(diffColor(dest.pixel(4, 3), 0x7f007f3f) <= error); // b.
            QVERIFY(diffColor(dest.pixel(4, 3), 0x7f007f3f) <= error); // b.
            QVERIFY(diffColor(dest.pixel(4, 4), 0x7f00007f) <= error); // b
            QVERIFY(diffColor(dest.pixel(4, 0), 0) <= 0); // 0
       }
    } else {
        QVERIFY(diffColor(dest.pixel(0, 0), 0xff000000) <= 0);
        QVERIFY(diffColor(dest.pixel(1, 1), 0xff007f00) <= error);
    }
}

void tst_QPainter::imageBlending_clipped()
{
    QImage src(20, 20, QImage::Format_RGB16);
    QPainter p(&src);
    p.fillRect(src.rect(), Qt::red);
    p.end();

    QImage dst(40, 20, QImage::Format_RGB16);
    p.begin(&dst);
    p.fillRect(dst.rect(), Qt::white);
    p.end();

    QImage expected = dst;

    p.begin(&dst);
    p.setClipRect(QRect(23, 0, 20, 20));

    // should be completely clipped
    p.drawImage(QRectF(3, 0, 20, 20), src);
    p.end();

    // dst should be left unchanged
    QCOMPARE(dst, expected);
}

void tst_QPainter::paintOnNullPixmap()
{
    QPixmap pix(16, 16);

    QPixmap textPixmap;
    QPainter p(&textPixmap);
    p.drawPixmap(10, 10, pix);
    p.end();

    QPixmap textPixmap2(16,16);
    p.begin(&textPixmap2);
    p.end();
}

void tst_QPainter::checkCompositionMode()
{
    QImage refImage(50,50,QImage::Format_ARGB32);
    QPainter painter(&refImage);
    painter.fillRect(QRect(0,0,50,50),Qt::blue);

    QImage testImage(50,50,QImage::Format_ARGB32);
    QPainter p(&testImage);
    p.fillRect(QRect(0,0,50,50),Qt::red);
    p.save();
    p.setCompositionMode(QPainter::CompositionMode_SourceOut);
    p.restore();
    p.fillRect(QRect(0,0,50,50),Qt::blue);

    QCOMPARE(refImage.pixel(20,20),testImage.pixel(20,20));
}

static QLinearGradient inverseGradient(QLinearGradient g)
{
    QLinearGradient g2 = g;

    const QGradientStops stops = g.stops();

    QGradientStops inverse;
    for (const QGradientStop &stop : stops)
        inverse << QGradientStop(1 - stop.first, stop.second);

    g2.setStops(inverse);
    return g2;
}

void tst_QPainter::linearGradientSymmetry_data()
{
    QTest::addColumn<QGradientStops>("stops");

    if (sizeof(qreal) != sizeof(float)) {
        QGradientStops stops = {{qreal(0.0), QColor(Qt::blue)},
                                {qreal(0.2), QColor(220, 220, 220, 0)},
                                {qreal(0.6), QColor(Qt::red)},
                                {qreal(0.9), QColor(220, 220, 220, 255)},
                                {qreal(1.0), QColor(Qt::black)}};
        QTest::newRow("multiple stops") << stops;
    }

    {
        QGradientStops stops = {{qreal(0.0), QColor(Qt::blue)},
                                {qreal(1.0), QColor(Qt::black)}};
        QTest::newRow("two stops") << stops;
    }

    if (sizeof(qreal) != sizeof(float)) {
        QGradientStops stops = {{qreal(0.3), QColor(Qt::blue)},
                                {qreal(0.6), QColor(Qt::black)}};
        QTest::newRow("two stops 2") << stops;
    }
}

void tst_QPainter::linearGradientSymmetry()
{
    QFETCH(QGradientStops, stops);

    QImage a(64, 8, QImage::Format_ARGB32_Premultiplied);
    QImage b(64, 8, QImage::Format_ARGB32_Premultiplied);

    a.fill(0);
    b.fill(0);

    QLinearGradient gradient(QRectF(b.rect()).topLeft(), QRectF(b.rect()).topRight());
    gradient.setStops(stops);

    QPainter pa(&a);
    pa.fillRect(a.rect(), gradient);
    pa.end();

    QPainter pb(&b);
    pb.fillRect(b.rect(), inverseGradient(gradient));
    pb.end();

    b = b.flipped(Qt::Horizontal | Qt::Vertical);
    QCOMPARE(a, b);
}

void tst_QPainter::gradientPixelFormat_data()
{
    QTest::addColumn<QImage::Format>("format");

    QTest::newRow("argb32") << QImage::Format_ARGB32;
    QTest::newRow("rgb32") << QImage::Format_RGB32;
    QTest::newRow("rgb888") << QImage::Format_RGB888;
    QTest::newRow("rgbx8888") << QImage::Format_RGBX8888;
    QTest::newRow("rgba8888") << QImage::Format_RGBA8888;
    QTest::newRow("rgba8888_pm") << QImage::Format_RGBA8888_Premultiplied;
    QTest::newRow("rgbx64") << QImage::Format_RGBX64;
    QTest::newRow("rgba64_pm") << QImage::Format_RGBA64_Premultiplied;
}

void tst_QPainter::gradientPixelFormat()
{
    QFETCH(QImage::Format, format);

    QImage a(8, 64, QImage::Format_ARGB32_Premultiplied);
    QImage b(8, 64, format);

    QGradientStops stops = {{qreal(0.0), QColor(Qt::blue)},
                            {qreal(0.3), QColor(Qt::red)},
                            {qreal(0.6), QColor(Qt::green)},
                            {qreal(1.0), QColor(Qt::black)}};

    a.fill(0);
    b.fill(0);

    QLinearGradient gradient(QRectF(b.rect()).topLeft(), QRectF(b.rect()).bottomLeft());
    gradient.setStops(stops);

    QPainter pa(&a);
    pa.fillRect(a.rect(), gradient);
    pa.end();

    QPainter pb(&b);
    pb.fillRect(b.rect(), gradient);
    pb.end();

    QCOMPARE(a, b.convertToFormat(QImage::Format_ARGB32_Premultiplied));
}

void tst_QPainter::radialGradient_QTBUG120332_ubsan()
{
    // Check if Radial Gradient will cause division by zero or not when
    // the center point coincide with the focal point.
    QImage image(8, 8, QImage::Format_ARGB32_Premultiplied);
    QPainter painter(&image);

    QPointF center(0.5, 0.5);
    QPointF focal(0.5, 0.5);
    QRadialGradient gradient(center, 0.5, focal, 0.5);
    gradient.setColorAt(0, Qt::blue);
    gradient.setColorAt(1, Qt::red);
    painter.fillRect(image.rect(), QBrush(gradient));
}

void tst_QPainter::radialGradient_QTBUG130992_crash()
{
    // Check if Radial Gradient will crash on extreme values
    // The crash was found by oss-fuzz, see
    // https://issues.oss-fuzz.com/issues/42533347
    QImage image(8, 8, QImage::Format_ARGB32_Premultiplied);
    QPainter painter(&image);

    constexpr qreal hugeValue = 1.1E37;
    QRadialGradient gradient(hugeValue, 0.5, 0.5, hugeValue, 0.5);
    painter.fillRect(image.rect(), QBrush(gradient));
}

void tst_QPainter::gradientInterpolation()
{
    QImage image(256, 8, QImage::Format_ARGB32_Premultiplied);
    QPainter painter;

    QLinearGradient gradient(QRectF(image.rect()).topLeft(), QRectF(image.rect()).topRight());
    gradient.setColorAt(0.0, QColor(255, 0, 0, 0));
    gradient.setColorAt(1.0, Qt::blue);

    image.fill(0);
    painter.begin(&image);
    painter.fillRect(image.rect(), gradient);
    painter.end();

    const QRgb *line = reinterpret_cast<QRgb *>(image.scanLine(3));

    for (int i = 0; i < 256; ++i) {
        QCOMPARE(qAlpha(line[i]), qBlue(line[i])); // bright blue
        QVERIFY(qAbs(qAlpha(line[i]) - i) < 3); // linear alpha
        QCOMPARE(qRed(line[i]), 0); // no red component
        QCOMPARE(qGreen(line[i]), 0); // no green component
    }

    gradient.setInterpolationMode(QGradient::ComponentInterpolation);

    image.fill(0);
    painter.begin(&image);
    painter.fillRect(image.rect(), gradient);
    painter.end();

    for (int i = 1; i < 256; ++i) {
        if (i < 128) {
            QVERIFY(qRed(line[i]) >= qBlue(line[i])); // red is dominant
        } else {
            QVERIFY(qRed(line[i]) <= qBlue(line[i])); // blue is dominant
        }
        QVERIFY((qRed(line[i]) - 0.5) * (qAlpha(line[i - 1]) - 0.5) <= (qRed(line[i - 1]) + 0.5) * (qAlpha(line[i]) + 0.5)); // decreasing red
        QVERIFY((qBlue(line[i]) + 0.5) * (qAlpha(line[i - 1]) + 0.5) >= (qBlue(line[i - 1]) - 0.5) * (qAlpha(line[i]) - 0.5)); // increasing blue
        QVERIFY(qAbs(qAlpha(line[i]) - i) < 3); // linear alpha
        QCOMPARE(qGreen(line[i]), 0); // no green component
    }
}

#if QT_CONFIG(raster_64bit)
void tst_QPainter::linearGradientRgb30_data()
{
    QTest::addColumn<QColor>("stop0");
    QTest::addColumn<QColor>("stop1");

    QTest::newRow("white->black") << QColor(Qt::white) << QColor(Qt::black);
    QTest::newRow("blue->black") << QColor(Qt::blue) << QColor(Qt::black);
    QTest::newRow("white->red") << QColor(Qt::white) << QColor(Qt::red);
}

void tst_QPainter::linearGradientRgb30()
{
    QFETCH(QColor, stop0);
    QFETCH(QColor, stop1);

    QLinearGradient gradient(0, 0, 1000, 1);
    gradient.setColorAt(0.0, stop0);
    gradient.setColorAt(1.0, stop1);

    QImage image(1000, 1, QImage::Format_RGB30);
    QPainter painter(&image);
    painter.fillRect(image.rect(), gradient);
    painter.end();

    for (int i = 1; i < 1000; ++i) {
        QColor p1 = image.pixelColor(i - 1, 0);
        QColor p2 = image.pixelColor(i, 0);
        QVERIFY(p1 != p2);
        QVERIFY(qGray(p1.rgb()) >= qGray(p2.rgb()));
    }
}

void tst_QPainter::radialGradientRgb30_data()
{
    linearGradientRgb30_data();
}

void tst_QPainter::radialGradientRgb30()
{
    QFETCH(QColor, stop0);
    QFETCH(QColor, stop1);

    QRadialGradient gradient(0, 0, 1000);
    gradient.setColorAt(0.0, stop0);
    gradient.setColorAt(1.0, stop1);

    QImage image(1000, 1, QImage::Format_A2BGR30_Premultiplied);
    QPainter painter(&image);
    painter.fillRect(image.rect(), gradient);
    painter.end();

    for (int i = 1; i < 1000; ++i) {
        QColor p1 = image.pixelColor(i - 1, 0);
        QColor p2 = image.pixelColor(i, 0);
        QVERIFY(p1 != p2);
        QVERIFY(qGray(p1.rgb()) >= qGray(p2.rgb()));
    }
}
#endif

void tst_QPainter::drawPolygon()
{
    QImage img(128, 128, QImage::Format_ARGB32_Premultiplied);

    QPainterPathStroker stroker;
    stroker.setWidth(1.5);

    QPainterPath path;
    path.moveTo(2, 34);
    path.lineTo(34, 2);

    QPolygonF poly = stroker.createStroke(path).toFillPolygon();

    img.fill(0xffffffff);
    QPainter p(&img);
    p.setRenderHint(QPainter::Antialiasing);
    p.setBrush(Qt::red);
    p.setPen(Qt::NoPen);
    p.drawPolygon(poly);
    p.translate(64, 64);
    p.drawPolygon(poly);
    p.end();

    QImage a = img.copy();

    img.fill(0xffffffff);
    p.begin(&img);
    p.setRenderHint(QPainter::Antialiasing);
    p.setBrush(Qt::red);
    p.setPen(Qt::NoPen);
    p.translate(64, 64);
    p.drawPolygon(poly);
    p.resetTransform();
    p.drawPolygon(poly);
    p.end();

    QCOMPARE(a, img);
}

void tst_QPainter::inactivePainter()
{
    // This test succeeds if it doesn't segfault.

    QPainter p;
    QPainterPath path;
    QRegion region(QRect(20, 20, 60, 40));
    QPolygonF polygon(QList<QPointF>() << QPointF(0, 0) << QPointF(12, 0) << QPointF(8, 6));
    path.addPolygon(polygon);

    p.save();
    p.restore();

    p.background();
    p.setBackground(QBrush(Qt::blue));

    p.brush();
    p.setBrush(Qt::red);
    p.setBrush(Qt::NoBrush);
    p.setBrush(QBrush(Qt::white, Qt::DiagCrossPattern));

    p.backgroundMode();
    p.setBackgroundMode(Qt::OpaqueMode);

    p.boundingRect(QRectF(0, 0, 100, 20), Qt::AlignCenter, QLatin1String("Hello, World!"));
    p.boundingRect(QRect(0, 0, 100, 20), Qt::AlignCenter, QLatin1String("Hello, World!"));

    p.brushOrigin();
    p.setBrushOrigin(QPointF(12, 34));
    p.setBrushOrigin(QPoint(12, 34));

    p.clipPath();
    p.clipRegion();
    p.hasClipping();
    p.setClipPath(path);
    p.setClipRect(QRectF(42, 42, 42, 42));
    p.setClipRect(QRect(42, 42, 42, 42));
    p.setClipRegion(region);
    p.setClipping(true);

    p.combinedTransform();

    p.compositionMode();
    p.setCompositionMode(QPainter::CompositionMode_Plus);

    p.device();
    p.deviceTransform();

    p.font();
    p.setFont(QFont(QLatin1String("Times"), 24));

    p.fontInfo();
    p.fontMetrics();

    p.layoutDirection();
    p.setLayoutDirection(Qt::RightToLeft);

    p.opacity();
    p.setOpacity(0.75);

    p.pen();
    p.setPen(QPen(Qt::red));
    p.setPen(Qt::green);
    p.setPen(Qt::NoPen);

    p.renderHints();
    p.setRenderHint(QPainter::Antialiasing, true);
    p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, false);

    p.resetTransform();
    p.rotate(1);
    p.scale(2, 2);
    p.shear(-1, 1);
    p.translate(3, 14);

    p.viewTransformEnabled();
    p.setViewTransformEnabled(true);

    p.viewport();
    p.setViewport(QRect(10, 10, 620, 460));

    p.window();
    p.setWindow(QRect(10, 10, 620, 460));

    p.worldTransform();
    p.setWorldTransform(QTransform().translate(43, 21), true);

    p.setWorldMatrixEnabled(true);

    p.transform();
    p.setTransform(QTransform().translate(12, 34), true);

    p.worldTransform();
    p.setWorldTransform(QTransform().scale(0.5, 0.5), true);
}

bool testCompositionMode(int src, int dst, int expected, QPainter::CompositionMode op, qreal opacity = 1.0)
{
    // The test image needs to be large enough to test SIMD code
    const QSize imageSize(100, 100);

    QImage actual(imageSize, QImage::Format_ARGB32_Premultiplied);
    actual.fill(QColor(dst, dst, dst).rgb());

    QPainter p(&actual);
    p.setCompositionMode(op);
    p.setOpacity(opacity);
    p.fillRect(QRect(QPoint(), imageSize), QColor(src, src, src));
    p.end();

    if (qRed(actual.pixel(0, 0)) != expected) {
        qDebug("Fail: mode %d, src[%d] dst [%d] actual [%d] expected [%d]", op,
               src, dst, qRed(actual.pixel(0, 0)), expected);
        return false;
    } else {
        QImage refImage(imageSize, QImage::Format_ARGB32_Premultiplied);
        refImage.fill(QColor(expected, expected, expected).rgb());
        return actual == refImage;
    }
}

void tst_QPainter::extendedBlendModes()
{
    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Plus));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Plus));
    QVERIFY(testCompositionMode(127, 128, 255, QPainter::CompositionMode_Plus));
    QVERIFY(testCompositionMode(127,   0, 127, QPainter::CompositionMode_Plus));
    QVERIFY(testCompositionMode(  0, 127, 127, QPainter::CompositionMode_Plus));
    QVERIFY(testCompositionMode(255,   0, 255, QPainter::CompositionMode_Plus));
    QVERIFY(testCompositionMode(  0, 255, 255, QPainter::CompositionMode_Plus));
    QVERIFY(testCompositionMode(128, 128, 255, QPainter::CompositionMode_Plus));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(126, 128, 165, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(127,   0,  38, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(  0, 127, 127, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(255,   0,  76, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(  0, 255, 255, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(128, 128, 166, QPainter::CompositionMode_Plus, 0.3));
    QVERIFY(testCompositionMode(186, 200, 255, QPainter::CompositionMode_Plus, 0.3));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Multiply));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Multiply));
    QVERIFY(testCompositionMode(127, 255, 127, QPainter::CompositionMode_Multiply));
    QVERIFY(testCompositionMode(255, 127, 127, QPainter::CompositionMode_Multiply));
    QVERIFY(testCompositionMode( 63, 255,  63, QPainter::CompositionMode_Multiply));
    QVERIFY(testCompositionMode(255,  63,  63, QPainter::CompositionMode_Multiply));
    QVERIFY(testCompositionMode(127, 127,  63, QPainter::CompositionMode_Multiply));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Screen));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Screen));
    QVERIFY(testCompositionMode( 63, 255, 255, QPainter::CompositionMode_Screen));
    QVERIFY(testCompositionMode(255,  63, 255, QPainter::CompositionMode_Screen));
    QVERIFY(testCompositionMode( 63,   0,  63, QPainter::CompositionMode_Screen));
    QVERIFY(testCompositionMode(  0,  63,  63, QPainter::CompositionMode_Screen));
    QVERIFY(testCompositionMode(127, 127, 191, QPainter::CompositionMode_Screen));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Overlay));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Overlay));
    QVERIFY(testCompositionMode( 63,  63,  31, QPainter::CompositionMode_Overlay));
    QVERIFY(testCompositionMode( 63, 255, 255, QPainter::CompositionMode_Overlay));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Darken));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Darken));
    QVERIFY(testCompositionMode( 63,  63,  63, QPainter::CompositionMode_Darken));
    QVERIFY(testCompositionMode( 63, 255,  63, QPainter::CompositionMode_Darken));
    QVERIFY(testCompositionMode(255,  63,  63, QPainter::CompositionMode_Darken));
    QVERIFY(testCompositionMode( 63, 127,  63, QPainter::CompositionMode_Darken));
    QVERIFY(testCompositionMode(127,  63,  63, QPainter::CompositionMode_Darken));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_Lighten));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Lighten));
    QVERIFY(testCompositionMode( 63,  63,  63, QPainter::CompositionMode_Lighten));
    QVERIFY(testCompositionMode( 63, 255, 255, QPainter::CompositionMode_Lighten));
    QVERIFY(testCompositionMode(255,  63, 255, QPainter::CompositionMode_Lighten));
    QVERIFY(testCompositionMode( 63, 127, 127, QPainter::CompositionMode_Lighten));
    QVERIFY(testCompositionMode(127,  63, 127, QPainter::CompositionMode_Lighten));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_ColorDodge));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_ColorDodge));
    QVERIFY(testCompositionMode( 63, 127, 169, QPainter::CompositionMode_ColorDodge));
    QVERIFY(testCompositionMode(191, 127, 255, QPainter::CompositionMode_ColorDodge));
    QVERIFY(testCompositionMode(127, 191, 255, QPainter::CompositionMode_ColorDodge));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_ColorBurn));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_ColorBurn));
    QVERIFY(testCompositionMode(127, 127,   0, QPainter::CompositionMode_ColorBurn));
    QVERIFY(testCompositionMode(128, 128,   2, QPainter::CompositionMode_ColorBurn));
    QVERIFY(testCompositionMode(191, 127,  84, QPainter::CompositionMode_ColorBurn));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_HardLight));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_HardLight));
    QVERIFY(testCompositionMode(127, 127, 127, QPainter::CompositionMode_HardLight));
    QVERIFY(testCompositionMode( 63,  63,  31, QPainter::CompositionMode_HardLight));
    QVERIFY(testCompositionMode(127,  63,  63, QPainter::CompositionMode_HardLight));

    QVERIFY(testCompositionMode(255, 255, 255, QPainter::CompositionMode_SoftLight));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_SoftLight));
    QVERIFY(testCompositionMode(127, 127, 126, QPainter::CompositionMode_SoftLight));
    QVERIFY(testCompositionMode( 63,  63,  39, QPainter::CompositionMode_SoftLight));
    QVERIFY(testCompositionMode(127,  63,  62, QPainter::CompositionMode_SoftLight));

    QVERIFY(testCompositionMode(255, 255,   0, QPainter::CompositionMode_Difference));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Difference));
    QVERIFY(testCompositionMode(255,   0, 255, QPainter::CompositionMode_Difference));
    QVERIFY(testCompositionMode(127, 127,   0, QPainter::CompositionMode_Difference));
    QVERIFY(testCompositionMode(127, 128,   1, QPainter::CompositionMode_Difference));
    QVERIFY(testCompositionMode(127,  63,  64, QPainter::CompositionMode_Difference));
    QVERIFY(testCompositionMode(  0, 127, 127, QPainter::CompositionMode_Difference));

    QVERIFY(testCompositionMode(255, 255,   0, QPainter::CompositionMode_Exclusion));
    QVERIFY(testCompositionMode(  0,   0,   0, QPainter::CompositionMode_Exclusion));
    QVERIFY(testCompositionMode(255,   0, 255, QPainter::CompositionMode_Exclusion));
    QVERIFY(testCompositionMode(127, 127, 127, QPainter::CompositionMode_Exclusion));
    QVERIFY(testCompositionMode( 63, 127, 127, QPainter::CompositionMode_Exclusion));
    QVERIFY(testCompositionMode( 63,  63,  95, QPainter::CompositionMode_Exclusion));
    QVERIFY(testCompositionMode(191, 191,  96, QPainter::CompositionMode_Exclusion));
}

void tst_QPainter::zeroOpacity()
{
    QImage source(1, 1, QImage::Format_ARGB32_Premultiplied);
    source.fill(0xffffffff);

    QImage target(1, 1, QImage::Format_RGB32);
    target.fill(0xff000000);

    QPainter p(&target);
    p.setOpacity(0.0);
    p.drawImage(0, 0, source);
    p.end();

    QCOMPARE(target.pixel(0, 0), 0xff000000);
}

void tst_QPainter::clippingBug()
{
    QImage img(32, 32, QImage::Format_ARGB32_Premultiplied);
    img.fill(0);

    QImage expected = img;
    QPainter p(&expected);
    p.fillRect(1, 1, 30, 30, Qt::red);
    p.end();

    QPainterPath path;
    path.addRect(1, 1, 30, 30);
    path.addRect(1, 1, 30, 30);
    path.addRect(1, 1, 30, 30);

    p.begin(&img);
    p.setClipPath(path);
    p.fillRect(0, 0, 32, 32, Qt::red);
    p.end();

    QCOMPARE(img, expected);
}

void tst_QPainter::emptyClip()
{
    QImage img(64, 64, QImage::Format_ARGB32_Premultiplied);
    QPainter p(&img);
    p.setRenderHints(QPainter::Antialiasing);
    p.setClipRect(0, 32, 64, 0);
    p.fillRect(0, 0, 64, 64, Qt::white);

    QPainterPath path;
    path.lineTo(64, 0);
    path.lineTo(64, 64);
    path.lineTo(40, 64);
    path.lineTo(40, 80);
    path.lineTo(0, 80);

    p.fillPath(path, Qt::green);
}

void tst_QPainter::drawImage_1x1()
{
    QImage source(1, 1, QImage::Format_ARGB32_Premultiplied);
    source.fill(0xffffffff);

    QImage img(32, 32, QImage::Format_ARGB32_Premultiplied);
    img.fill(0xff000000);
    QPainter p(&img);
    p.drawImage(QRectF(0.9, 0.9, 32, 32), source);
    p.end();

    QImage expected = img;
    expected.fill(0xff000000);
    p.begin(&expected);
    p.fillRect(1, 1, 31, 31, Qt::white);
    p.end();

    QCOMPARE(img, expected);
}

void tst_QPainter::taskQT4444_dontOverflowDashOffset()
{
    QPainter p;

    QPen pen;
    pen.setWidth(2);
    pen.setStyle(Qt::DashDotLine);

    QPointF point[4];
    point[0] = QPointF(182.50868749707968,347.78457234212630);
    point[1] = QPointF(182.50868749707968,107.22501998401277);
    point[2] = QPointF(182.50868749707968,107.22501998401277);
    point[3] = QPointF(520.46600762283651,107.22501998401277);

    QImage crashImage(QSize(1000, 120), QImage::Format_ARGB32_Premultiplied);
    p.begin(&crashImage);
    p.setPen(pen);
    p.drawLines(point, 2);
    p.end();

    QVERIFY(true); // Don't crash
}

void tst_QPainter::painterBegin()
{
    QImage nullImage;
    QImage indexed8Image(16, 16, QImage::Format_Indexed8);
    QImage rgb32Image(16, 16, QImage::Format_RGB32);
    QImage argb32Image(16, 16, QImage::Format_ARGB32_Premultiplied);

    QPainter p;

    // Painting on null image should fail.
    QVERIFY(!p.begin(&nullImage));

    // Check that the painter is not messed up by using it on another image.
    QVERIFY(p.begin(&rgb32Image));
    QVERIFY(p.end());

    // If painting on indexed8 image fails, the painter state should still be OK.
    if (p.begin(&indexed8Image))
        QVERIFY(p.end());
    QVERIFY(p.begin(&rgb32Image));
    QVERIFY(p.end());

    // Try opening a painter on the two different images.
    QVERIFY(p.begin(&rgb32Image));
    QVERIFY(!p.begin(&argb32Image));
    QVERIFY(p.end());

    // Try opening two painters on the same image.
    QVERIFY(p.begin(&rgb32Image));
    QPainter q;
    QVERIFY(!q.begin(&rgb32Image));
    QVERIFY(!q.end());
    QVERIFY(p.end());

    // Try ending an inactive painter.
    QVERIFY(!p.end());
}

void tst_QPainter::setPenColor(QPainter& p)
{
    p.setPen(Qt::NoPen);

    // Setting color, then style
    // Should work even though the pen is "NoPen with color", temporarily.
    QPen newPen(p.pen());
    newPen.setColor(Qt::red);
    QCOMPARE(p.pen().style(), newPen.style());
    QCOMPARE(p.pen().style(), Qt::NoPen);
    p.setPen(newPen);

    QCOMPARE(p.pen().color().name(), QString("#ff0000"));

    QPen newPen2(p.pen());
    newPen2.setStyle(Qt::SolidLine);
    p.setPen(newPen2);

    QCOMPARE(p.pen().color().name(), QString("#ff0000"));
}

void tst_QPainter::setPenColorOnImage()
{
    QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied);
    QPainter p(&img);
    setPenColor(p);
}

void tst_QPainter::setPenColorOnPixmap()
{
    QPixmap pix(10, 10);
    QPainter p(&pix);
    setPenColor(p);
}

#ifndef QT_NO_WIDGETS
class TestProxy : public QGraphicsProxyWidget
{
public:
    TestProxy() : QGraphicsProxyWidget() {}
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
    {
        QGraphicsProxyWidget::paint(painter, option, widget);
        deviceTransform = painter->deviceTransform();
    }
    QTransform deviceTransform;
};

class TestWidget : public QWidget
{
Q_OBJECT
public:
    TestWidget() : QWidget(), painted(false) {}
    void paintEvent(QPaintEvent *) override
    {
        QPainter p(this);
        deviceTransform = p.deviceTransform();
        worldTransform = p.worldTransform();
        painted = true;
    }
    QTransform deviceTransform;
    QTransform worldTransform;
    bool painted;
};

void tst_QPainter::QTBUG5939_attachPainterPrivate()
{
    QWidget *w = new QWidget();
    QGraphicsScene *scene = new QGraphicsScene();
    QGraphicsView *view = new QGraphicsView(scene, w);
    view->move(50 ,50);
    TestProxy *proxy = new TestProxy();
    TestWidget *widget = new TestWidget();
    proxy->setWidget(widget);
    scene->addItem(proxy);
    proxy->setTransform(QTransform().rotate(45));
    w->resize(scene->sceneRect().size().toSize());

    w->show();
    QTRY_VERIFY(widget->painted);

    QVERIFY(widget->worldTransform.isIdentity());
    QCOMPARE(widget->deviceTransform, proxy->deviceTransform);
}
#endif

void tst_QPainter::clipBoundingRect()
{
    QPixmap pix(500, 500);

    QPainter p(&pix);

    // Test a basic rectangle
    p.setClipRect(100, 100, 200, 100);
    QVERIFY(p.clipBoundingRect().contains(QRectF(100, 100, 200, 100)));
    QVERIFY(!p.clipBoundingRect().contains(QRectF(50, 50, 300, 200)));
    p.setClipRect(120, 120, 20, 20, Qt::IntersectClip);
    QVERIFY(p.clipBoundingRect().contains(QRect(120, 120, 20, 20)));
    QVERIFY(!p.clipBoundingRect().contains(QRectF(100, 100, 200, 100)));

    // Test a basic float rectangle
    p.setClipRect(QRectF(100, 100, 200, 100));
    QVERIFY(p.clipBoundingRect().contains(QRectF(100, 100, 200, 100)));
    QVERIFY(!p.clipBoundingRect().contains(QRectF(50, 50, 300, 200)));
    p.setClipRect(QRectF(120, 120, 20, 20), Qt::IntersectClip);
    QVERIFY(p.clipBoundingRect().contains(QRect(120, 120, 20, 20)));
    QVERIFY(!p.clipBoundingRect().contains(QRectF(100, 100, 200, 100)));

    // Test a basic path + region
    QPainterPath path;
    path.addRect(100, 100, 200, 100);
    p.setClipPath(path);
    QVERIFY(p.clipBoundingRect().contains(QRectF(100, 100, 200, 100)));
    QVERIFY(!p.clipBoundingRect().contains(QRectF(50, 50, 300, 200)));
    p.setClipRegion(QRegion(120, 120, 20, 20), Qt::IntersectClip);
    QVERIFY(p.clipBoundingRect().contains(QRect(120, 120, 20, 20)));
    QVERIFY(!p.clipBoundingRect().contains(QRectF(100, 100, 200, 100)));

    p.setClipRect(0, 0, 500, 500);
    p.translate(250, 250);
    for (int i=0; i<360; ++i) {
        p.rotate(1);
        p.setClipRect(-100, -100, 200, 200, Qt::IntersectClip);
    }
    QVERIFY(p.clipBoundingRect().contains(QRectF(-100, -100, 200, 200)));
    QVERIFY(!p.clipBoundingRect().contains(QRectF(-250, -250, 500, 500)));

}

void tst_QPainter::transformedClip()
{
    QImage img(8, 4, QImage::Format_ARGB32_Premultiplied);
    QImage img2(img.size(), img.format());
    QRect clip(0, 0, 2, 1);
    QTransform xf;
    xf.translate(0.2, 0);
    xf.scale(2.2, 1);
    // setClipRect(QRectF)
    {
        img.fill(Qt::green);
        QPainter p(&img);
        p.setTransform(xf);
        p.setClipRect(QRectF(clip));
        p.fillRect(img.rect(), Qt::white);
    }
    // setClipRect(QRect)
    {
        img2.fill(Qt::green);
        QPainter p(&img2);
        p.setTransform(xf);
        p.setClipRect(clip);
        p.fillRect(img2.rect(), Qt::white);
        QCOMPARE(img, img2);
    }
    // setClipRegion
    {
        img2.fill(Qt::green);
        QPainter p(&img2);
        p.setTransform(xf);
        p.setClipRegion(QRegion(clip) + QRect(0, 3, 1, 1));  // dummy extra rect to avoid single-rect codepath
        p.fillRect(img2.rect(), Qt::white);
        QCOMPARE(img.copy(0, 0, 8, 2), img2.copy(0, 0, 8, 2));
    }
    // setClipPath
    {
        img2.fill(Qt::green);
        QPainter p(&img2);
        p.setTransform(xf);
        QPainterPath path;
        path.addRect(clip);
        p.setClipPath(path);
        p.fillRect(img2.rect(), Qt::white);
        QCOMPARE(img, img2);
    }
}

void tst_QPainter::scaledClipConsistency_data()
{
    QTest::addColumn<ClipType>("clipType");

    QTest::newRow("clipRect") << ClipRect;
    QTest::newRow("clipRectF") << ClipRectF;
    QTest::newRow("clipRegionSingle") << ClipRegionSingle;
    QTest::newRow("clipRegionMulti") << ClipRegionMulti;
    QTest::newRow("clipPathR") << ClipPathR;
    QTest::newRow("clipPath") << ClipPath;
}

void tst_QPainter::scaledClipConsistency()
{
    QFETCH(ClipType, clipType);

    const QList<QRect> clipRects = {
        // Varying odd and even coordinates and width/height
        QRect(1, 1, 7, 8),
        QRect(8, 0, 8, 9),
        QRect(0, 9, 8, 7),
        QRect(8, 9, 8, 7),
    };
    // Assert that these are edge to edge:
    QPointF center = QRectF(clipRects[0]).bottomRight();
    Q_ASSERT(QRectF(clipRects[1]).bottomLeft() == center);
    Q_ASSERT(QRectF(clipRects[2]).topRight() == center);
    Q_ASSERT(QRectF(clipRects[3]).topLeft() == center);

    QRegion multiRegion;
    for (const QRect &clipRect : clipRects)
        multiRegion += clipRect;

    QColor fillColor(Qt::black);
    fillColor.setAlphaF(0.5);

    for (int i = 100; i <= 300; i++) {
        qreal dpr = qreal(i) / 100.0;
        QImage img(QSize(16, 16) * dpr, QImage::Format_RGB32);
        img.fill(Qt::white);
        img.setDevicePixelRatio(dpr);

        for (const QRect &clipRect : clipRects) {
            QPainter p(&img);
            switch (clipType) {
            case ClipRect:
                p.setClipRect(clipRect);
                break;
            case ClipRectF:
                p.setClipRect(QRectF(clipRect));
                break;
            case ClipRegionSingle:
                p.setClipRegion(QRegion(clipRect));
                break;
            case ClipRegionMulti:
                p.setClipRegion(multiRegion);
                break;
            case ClipPath:
                p.rotate(0.001); // Avoid the path being optimized to a rectf
                Q_FALLTHROUGH();
            case ClipPathR: {
                QPainterPath path;
                path.addRect(clipRect); // Will be recognized and converted back to a rectf
                p.setClipPath(path);
                break;
            }
            default:
                Q_ASSERT(false);
                break;
            }
            p.fillRect(p.window(), fillColor);
            if (clipType == ClipRegionMulti)
                break; // once is enough, we're not using the clipRect anyway
        }

        int qtWidth = img.width() / 4;
        int qtHeight = img.height() / 4;
        QPoint imgCenter = img.rect().center();
        const QRgb targetColor = img.pixel(qtWidth, qtHeight);

        // Test that there are no gaps or overlaps where the cliprects meet
        for (int offset = -2; offset <= 2; offset++) {
            QCOMPARE(img.pixel(imgCenter.x() + offset, qtHeight), targetColor);
            QCOMPARE(img.pixel(imgCenter.x() + offset, img.height() - qtHeight), targetColor);
            QCOMPARE(img.pixel(qtWidth, imgCenter.y() + offset), targetColor);
            QCOMPARE(img.pixel(img.width() - qtWidth, imgCenter.y() + offset), targetColor);
        }
    }
}

#if defined(Q_OS_MAC)
// Only Mac supports sub pixel positions in raster engine currently
void tst_QPainter::drawText_subPixelPositionsInRaster_qtbug5053()
{
    QFontMetricsF fm(qApp->font());

    QImage baseLine(fm.horizontalAdvance(QChar::fromLatin1('e')), fm.height(), QImage::Format_RGB32);
    baseLine.fill(Qt::white);
    {
        QPainter p(&baseLine);
        p.drawText(0, fm.ascent(), QString::fromLatin1("e"));
    }

    bool foundDifferentRasterization = false;
    for (int i=1; i<12; ++i) {
        QImage comparison(baseLine.size(), QImage::Format_RGB32);
        comparison.fill(Qt::white);

        {
            QPainter p(&comparison);
            p.drawText(QPointF(i / 12.0, fm.ascent()), QString::fromLatin1("e"));
        }

        if (comparison != baseLine) {
            foundDifferentRasterization = true;
            break;
        }
    }

    QVERIFY(foundDifferentRasterization);
}
#endif

void tst_QPainter::drawPointScaled()
{
    QImage image(32, 32, QImage::Format_RGB32);
    image.fill(0xffffffff);

    QPainter p(&image);

    p.scale(0.1, 0.1);

    QPen pen;
    pen.setWidth(1000);
    pen.setColor(Qt::red);

    p.setPen(pen);
    p.drawPoint(0, 0);
    p.end();

    QCOMPARE(image.pixel(16, 16), 0xffff0000);
}

class GradientProducer : public QThread
{
protected:
    void run() override;
};

void GradientProducer::run()
{
    QImage image(1, 1, QImage::Format_RGB32);
    QPainter p(&image);

    for (int i = 0; i < 1000; ++i) {
        QLinearGradient g;
        g.setColorAt(0, QColor(i % 256, 0, 0));
        g.setColorAt(1, Qt::white);

        p.fillRect(image.rect(), g);
    }
}

void tst_QPainter::QTBUG14614_gradientCacheRaceCondition()
{
    const int threadCount = 16;
    GradientProducer producers[threadCount];
    for (int i = 0; i < threadCount; ++i)
        producers[i].start();
    for (int i = 0; i < threadCount; ++i)
        producers[i].wait();
}

void tst_QPainter::drawTextOpacity()
{
    QImage image(32, 32, QImage::Format_RGB32);
    image.fill(0xffffffff);

    QPainter p(&image);
    p.setPen(QColor("#6F6F6F"));
    p.setOpacity(0.5);
    p.drawText(5, 30, QLatin1String("Qt"));
    p.end();

    QImage copy = image;
    image.fill(0xffffffff);

    p.begin(&image);
    p.setPen(QColor("#6F6F6F"));
    p.drawLine(-10, -10, -1, -1);
    p.setOpacity(0.5);
    p.drawText(5, 30, QLatin1String("Qt"));
    p.end();

    QCOMPARE(image, copy);
}

void tst_QPainter::drawPathOpacity()
{
    // make sure that drawing a non-opaque QPainterPath will yield the same
    // result as drawing its fill and outline separately, i.e. that the fill's
    // edge can be seen through the translucent outline
    // qtsvg relies on this behavior, so please inform its developers in
    // case you change it

    QImage image(32, 32, QImage::Format_RGB32);
    image.fill(Qt::white);
    QPainter p(&image);
    p.setOpacity(0.5);

    QPainterPath pp;
    pp.moveTo(5, 5);
    pp.lineTo(27, 5);
    pp.lineTo(5, 27);
    pp.closeSubpath();

    // only fill
    p.setPen(Qt::NoPen);
    p.setBrush(QColor("blue"));
    p.drawPath(pp);

    // only outline
    QPen pen;
    pen.setColor(QColor("yellow"));
    pen.setWidthF(8.);
    p.setPen(pen);
    p.setBrush(Qt::NoBrush);
    p.drawPath(pp);
    const QImage drawnInTwoPasses = image;

    // draw in one pass
    image.fill(Qt::white);
    p.setPen(pen);
    p.setBrush(QColor("blue"));
    p.drawPath(pp);

    QCOMPARE(image, drawnInTwoPasses);
}

void tst_QPainter::QTBUG17053_zeroDashPattern()
{
    QImage image(32, 32, QImage::Format_RGB32);
    image.fill(0xffffffff);

    QImage original = image;

    QList<qreal> pattern;
    pattern << qreal(0) << qreal(0);

    QPainter p(&image);
    QPen pen(Qt::black, 2.0);
    pen.setDashPattern(pattern);

    p.setPen(pen);
    p.drawLine(0, 0, image.width(), image.height());

    QCOMPARE(image, original);
}

void tst_QPainter::QTBUG38781_NoBrushAndQBitmap()
{
    QBitmap bitmap(10, 10);
    bitmap.fill(Qt::color0);
    QPainter p(&bitmap);
    p.setPen(Qt::color1);
    p.drawLine(0, 1, 9, 1); // at horizontal line at y=1
    p.setBrush(Qt::NoBrush);
    p.drawRect(0, 0, 9, 9); // a rect all around

    QRgb white = qRgb(0xff, 0xff, 0xff);
    QRgb black = qRgb(0, 0, 0);
    QImage image = bitmap.toImage();
    QCOMPARE(image.pixel(0, 0), black);
    QCOMPARE(image.pixel(5, 5), white);

    // Check that the rect didn't overwrite the line
    QCOMPARE(image.pixel(5, 1), black);
}

class TextDrawerThread : public QThread
{
public:
    void run() override;
    QImage rendering;
};

void TextDrawerThread::run()
{
    rendering = QImage(100, 100, QImage::Format_ARGB32_Premultiplied);
    rendering.fill(0);
    QPainter p(&rendering);
    p.fillRect(10, 10, 100, 100, Qt::blue);
    p.setPen(Qt::green);
    p.drawText(20, 20, "some text");
    p.end();
}

void tst_QPainter::drawTextOutsideGuiThread()
{
    QImage referenceRendering(100, 100, QImage::Format_ARGB32_Premultiplied);
    referenceRendering.fill(0);
    QPainter p(&referenceRendering);
    p.fillRect(10, 10, 100, 100, Qt::blue);
    p.setPen(Qt::green);
    p.drawText(20, 20, "some text");
    p.end();

    TextDrawerThread t;
    t.start();
    t.wait();

    QCOMPARE(referenceRendering, t.rendering);
}

void tst_QPainter::drawTextWithComplexBrush()
{
    QImage texture(10, 10, QImage::Format_ARGB32_Premultiplied);
    texture.fill(Qt::red);

    QImage image(100, 100, QImage::Format_ARGB32_Premultiplied);
    image.fill(Qt::white);
    QPainter p(&image);
    QFont f = p.font();
    f.setPixelSize(70);
    p.setFont(f);

    QBrush brush(Qt::white);
    brush.setTextureImage(texture);
    p.setPen(QPen(brush, 2));

    p.drawText(10, 10, "Hello World");

    int paintedPixels = getPaintedPixels(image, Qt::white);
    QVERIFY(paintedPixels > 0);
}

void tst_QPainter::QTBUG26013_squareCapStroke()
{
    QImage image(4, 4, QImage::Format_RGB32);

    QPainter p(&image);
    p.setPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap));

    for (int i = 0; i < 3; ++i) {
        qreal d = i / 3.0;

        image.fill(0xffffffff);

        p.drawLine(QLineF(0, d, 0, d + 2));
        p.drawLine(QLineF(1, d, 3, d));

        // ensure that a horizontal line and a vertical line with square cap round up (downwards) at the same time
        QCOMPARE(image.pixel(0, 0), image.pixel(1, 0));

        image.fill(0xffffffff);

        p.drawLine(QLineF(d, 0, d + 2, 0));
        p.drawLine(QLineF(d, 1, d, 3));

        // ensure that a vertical line and a horizontal line with square cap round up (to the right) at the same time
        QCOMPARE(image.pixel(0, 0), image.pixel(0, 1));
    }
}

void tst_QPainter::QTBUG25153_drawLine()
{
    QImage image(2, 2, QImage::Format_RGB32);

    for (Qt::PenCapStyle style : {Qt::FlatCap, Qt::SquareCap, Qt::RoundCap}) {
        image.fill(0xffffffff);
        QPainter p(&image);
        p.setPen(QPen(Qt::black, 0, Qt::SolidLine, style));
        p.drawLine(QLineF(0, 0, 0, 0));
        p.end();

        QCOMPARE(image.pixel(0, 0), 0xff000000);
        QCOMPARE(image.pixel(0, 1), 0xffffffff);
        QCOMPARE(image.pixel(1, 0), 0xffffffff);
    }
}

void tst_QPainter::blendARGBonRGB_data()
{
    QTest::addColumn<QImage::Format>("dst_format");
    QTest::addColumn<QImage::Format>("src_format");
    QTest::addColumn<QPainter::CompositionMode>("compositionMode");
    QTest::addColumn<QRgb>("color");
    QTest::addColumn<int>("expected_red");

    QTest::newRow("ARGB over ARGB32") << QImage::Format_ARGB32 << QImage::Format_ARGB32
                                      << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 127) << 127 ;
    QTest::newRow("ARGB_PM over ARGB32") << QImage::Format_ARGB32 << QImage::Format_ARGB32_Premultiplied
                                         << QPainter::CompositionMode_SourceOver<< qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source ARGB32") << QImage::Format_ARGB32 << QImage::Format_ARGB32
                                        << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 127) << 255;
    QTest::newRow("ARGB_PM source ARGB32") << QImage::Format_ARGB32 << QImage::Format_ARGB32_Premultiplied
                                           << QPainter::CompositionMode_Source << qRgba(127, 0, 0, 127) << 255;
    QTest::newRow("ARGB source-in ARGB32") << QImage::Format_ARGB32 << QImage::Format_ARGB32
                                           << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 127) << 255 ;
    QTest::newRow("ARGB_PM source-in ARGB32") << QImage::Format_ARGB32 << QImage::Format_ARGB32_Premultiplied
                                              << QPainter::CompositionMode_SourceIn << qRgba(127, 0, 0, 127) << 255;
    QTest::newRow("ARGB over RGBA8888") << QImage::Format_RGBA8888 << QImage::Format_ARGB32
                                        << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM over RGBA8888") << QImage::Format_RGBA8888 << QImage::Format_ARGB32_Premultiplied
                                           << QPainter::CompositionMode_SourceOver << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source RGBA8888") << QImage::Format_RGBA8888 << QImage::Format_ARGB32
                                          << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 127) << 255;
    QTest::newRow("ARGB_PM source RGBA8888") << QImage::Format_RGBA8888 << QImage::Format_ARGB32_Premultiplied
                                             << QPainter::CompositionMode_Source << qRgba(127, 0, 0, 127) << 255;
    QTest::newRow("ARGB source-in RGBA8888") << QImage::Format_RGBA8888 << QImage::Format_ARGB32
                                             << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 127) << 255;
    QTest::newRow("ARGB_PM source-in RGBA8888") << QImage::Format_RGBA8888 << QImage::Format_ARGB32_Premultiplied
                                                << QPainter::CompositionMode_SourceIn << qRgba(127, 0, 0, 127) << 255;
    // Only ARGB32 and RGBA8888 does inverse premultiply, on the rest over and source gives similar results:
    QTest::newRow("ARGB over RGB32") << QImage::Format_RGB32 << QImage::Format_ARGB32
                                     << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM over RGB32") << QImage::Format_RGB32 << QImage::Format_ARGB32_Premultiplied
                                        << QPainter::CompositionMode_SourceOver << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source RGB32") << QImage::Format_RGB32 << QImage::Format_ARGB32
                                       << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM source RGB32") << QImage::Format_RGB32 << QImage::Format_ARGB32_Premultiplied
                                          << QPainter::CompositionMode_Source << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source-in RGB32") << QImage::Format_RGB32 << QImage::Format_ARGB32
                                          << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM source-in RGB32") << QImage::Format_RGB32 << QImage::Format_ARGB32_Premultiplied
                                             << QPainter::CompositionMode_SourceIn << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB over RGB888") << QImage::Format_RGB888 << QImage::Format_ARGB32
                                      << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM over RGB888") << QImage::Format_RGB888 << QImage::Format_ARGB32_Premultiplied
                                         << QPainter::CompositionMode_SourceOver << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source RGB888") << QImage::Format_RGB888 << QImage::Format_ARGB32
                                        << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM source RGB888") << QImage::Format_RGB888 << QImage::Format_ARGB32_Premultiplied
                                           << QPainter::CompositionMode_Source << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source-in RGB888") << QImage::Format_RGB888 << QImage::Format_ARGB32
                                           << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM source-in RGB888") << QImage::Format_RGB888 << QImage::Format_ARGB32_Premultiplied
                                              << QPainter::CompositionMode_SourceIn << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB over RGBx8888") << QImage::Format_RGBX8888 << QImage::Format_ARGB32
                                        << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM over RGBx8888") << QImage::Format_RGBX8888 << QImage::Format_ARGB32_Premultiplied
                                           << QPainter::CompositionMode_SourceOver << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source RGBx8888") << QImage::Format_RGBX8888 << QImage::Format_ARGB32
                                          << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM source RGBx8888") << QImage::Format_RGBX8888 << QImage::Format_ARGB32_Premultiplied
                                             << QPainter::CompositionMode_Source << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB source-in RGBx8888") << QImage::Format_RGBX8888 << QImage::Format_ARGB32
                                             << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 127) << 127;
    QTest::newRow("ARGB_PM source-in RGBx8888") << QImage::Format_RGBX8888 << QImage::Format_ARGB32_Premultiplied
                                                << QPainter::CompositionMode_SourceIn << qRgba(127, 0, 0, 127) << 127;
    QTest::newRow("ARGB over RGB16") << QImage::Format_RGB16 << QImage::Format_ARGB32
                                     << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 127) << 123;
    QTest::newRow("ARGB_PM over RGB16") << QImage::Format_RGB16 << QImage::Format_ARGB32_Premultiplied
                                        << QPainter::CompositionMode_SourceOver << qRgba(127, 0, 0, 127) << 123;
    QTest::newRow("ARGB source RGB16") << QImage::Format_RGB16 << QImage::Format_ARGB32
                                       << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 127) << 123;
    QTest::newRow("ARGB_PM source RGB16") << QImage::Format_RGB16 << QImage::Format_ARGB32_Premultiplied
                                          << QPainter::CompositionMode_Source << qRgba(127, 0, 0, 127) << 123;
    QTest::newRow("ARGB source-in RGB16") << QImage::Format_RGB16 << QImage::Format_ARGB32
                                          << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 127) << 123;
    QTest::newRow("ARGB_PM source-in RGB16") << QImage::Format_RGB16 << QImage::Format_ARGB32_Premultiplied
                                             << QPainter::CompositionMode_SourceIn << qRgba(127, 0, 0, 127) << 123;
    QTest::newRow("ARGB over RGB666") << QImage::Format_RGB666 << QImage::Format_ARGB32
                                      << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 127) << 125;
    QTest::newRow("ARGB_PM over RGB666") << QImage::Format_RGB666 << QImage::Format_ARGB32_Premultiplied
                                         << QPainter::CompositionMode_SourceOver << qRgba(127, 0, 0, 127) << 125;
    QTest::newRow("ARGB source RGB666") << QImage::Format_RGB666 << QImage::Format_ARGB32
                                        << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 127) << 125;
    QTest::newRow("ARGB_PM source RGB666") << QImage::Format_RGB666 << QImage::Format_ARGB32_Premultiplied
                                           << QPainter::CompositionMode_Source << qRgba(127, 0, 0, 127) << 125;
    QTest::newRow("ARGB source-in RGB666") << QImage::Format_RGB666 << QImage::Format_ARGB32
                                           << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 127) << 125;
    QTest::newRow("ARGB_PM source-in RGB666") << QImage::Format_RGB666 << QImage::Format_ARGB32_Premultiplied
                                              << QPainter::CompositionMode_SourceIn << qRgba(127, 0, 0, 127) << 125;
    QTest::newRow("ARGB over RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32
                                     << QPainter::CompositionMode_SourceOver << qRgba(255, 0, 0, 85) << 85;
    QTest::newRow("ARGB_PM over RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32_Premultiplied
                                        << QPainter::CompositionMode_SourceOver << qRgba(85, 0, 0, 85) << 85;
#if QT_CONFIG(raster_64bit)
    QTest::newRow("ARGB@85 source RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32
                                          << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 85) << 85;
    QTest::newRow("ARGB@120 source RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32
                                           << QPainter::CompositionMode_Source << qRgba(255, 0, 0, 120) << 85;
#endif
    QTest::newRow("ARGB_PM@85 source RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32_Premultiplied
                                             << QPainter::CompositionMode_Source << qRgba(85, 0, 0, 85) << 85;
#if QT_CONFIG(raster_64bit)
    QTest::newRow("ARGB_PM@180 source RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32_Premultiplied
                                              << QPainter::CompositionMode_Source << qRgba(180, 0, 0, 180) << 170;
#endif
    QTest::newRow("ARGB source-in RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32
                                          << QPainter::CompositionMode_SourceIn << qRgba(255, 0, 0, 85) << 85;
    QTest::newRow("ARGB_PM source-in RGB30") << QImage::Format_RGB30 << QImage::Format_ARGB32_Premultiplied
                                             << QPainter::CompositionMode_SourceIn << qRgba(85, 0, 0, 85) << 85;
}

void tst_QPainter::blendARGBonRGB()
{
    QFETCH(QImage::Format, dst_format);
    QFETCH(QImage::Format, src_format);
    QFETCH(QPainter::CompositionMode, compositionMode);
    QFETCH(QRgb, color);
    QFETCH(int, expected_red);

    QImage imageRgb(16,16, dst_format);
    QImage imageArgb(16,16, src_format);
    QPainter painter;

    imageArgb.fill(color);

    imageRgb.fill(Qt::black);
    painter.begin(&imageRgb);
    painter.setCompositionMode(compositionMode);
    painter.drawImage(0, 0, imageArgb);
    painter.end();

    QCOMPARE(imageRgb.pixelColor(0,0).red(), expected_red);
}

enum CosmeticStrokerPaint
{
    Antialiasing,
    Dashing
};

static void paint_func(QPainter *p, CosmeticStrokerPaint type)
{
    p->save();
    switch (type) {
    case Antialiasing:
        p->setPen(Qt::black);
        p->setRenderHint(QPainter::Antialiasing);
        p->drawLine(4, 8, 42, 42);
        break;
    case Dashing:
        p->setPen(QPen(Qt::black, 1, Qt::DashLine, Qt::RoundCap, Qt::MiterJoin));
        p->drawLine(8, 8, 42, 8);
        p->drawLine(42, 8, 42, 42);
        p->drawLine(42, 42, 8, 42);
        p->drawLine(8, 42, 8, 8);
        break;
    default:
        Q_ASSERT(false);
        break;
    }
    p->restore();
}

Q_DECLARE_METATYPE(CosmeticStrokerPaint)

void tst_QPainter::cosmeticStrokerClipping_data()
{
    QTest::addColumn<CosmeticStrokerPaint>("paint");

    QTest::newRow("antialiasing_paint") << Antialiasing;
    QTest::newRow("dashing_paint") << Dashing;
}

void tst_QPainter::cosmeticStrokerClipping()
{
    QFETCH(CosmeticStrokerPaint, paint);

    QImage image(50, 50, QImage::Format_RGB32);
    image.fill(Qt::white);

    QPainter p(&image);
    paint_func(&p, paint);
    p.end();

    QImage old = image.copy();

    image.paintEngine()->setSystemClip(QRect(10, 0, image.width() - 10, image.height()));

    p.begin(&image);
    p.fillRect(image.rect(), Qt::white);
    paint_func(&p, paint);

    // doing same paint operation again with different system clip should not change the image
    QCOMPARE(old, image);

    old = image;

    p.setClipRect(QRect(20, 20, 30, 30));
    p.fillRect(image.rect(), Qt::white);
    paint_func(&p, paint);

    // ditto for regular clips
    QCOMPARE(old, image);
}

void tst_QPainter::RasterOp_NotDestination()
{
    QImage image(3, 3, QImage::Format_RGB32);
    image.fill(Qt::red);

    {
        QPainter p(&image);
        p.setCompositionMode(QPainter::RasterOp_NotDestination);
        p.fillRect(image.rect(), Qt::black);
    }

    uint pixel = image.pixel(1, 1);
    QCOMPARE(pixel, 0xff00ffff);
}

void tst_QPainter::drawTextNoHinting()
{
    {
        QImage image(250, 250, QImage::Format_RGB32);
        QPainter p(&image);
        QFont font("Arial", 8);
        font.setHintingPreference(QFont::PreferNoHinting);
        font.setStyleStrategy(QFont::PreferAntialias);
        p.setFont(font);
        p.drawText(image.rect(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz");
    }
    // Testing for a crash when DirectWrite is used on Windows
    QVERIFY(true);
}

void tst_QPainter::drawPolyline_data()
{
    QTest::addColumn<QList<QPointF>>("points");

    QTest::newRow("basic") << (QList<QPointF>()
                               << QPointF(10, 10) << QPointF(20, 10) << QPointF(20, 20));
    QTest::newRow("clipped") << (QList<QPointF>()
                                 << QPoint(-10, 100) << QPoint(-1, 100) << QPoint(-1, -2)
                                 << QPoint(100, -2) << QPoint(100, 40)); // QTBUG-31579
    QTest::newRow("shortsegment") << (QList<QPointF>()
                                      << QPoint(20, 100) << QPoint(20, 99) << QPoint(21, 99)
                                      << QPoint(21, 104)); // QTBUG-42398
    QTest::newRow("edge") << (QList<QPointF>() << QPointF(4.5, 121.6) << QPointF(9.4, 150.9)
                                               << QPointF(14.2, 184.8) << QPointF(19.1, 130.4));
}

void tst_QPainter::drawPolyline()
{
    QFETCH(QList<QPointF>, points);
    QImage images[2];

    for (int r = 0; r < 2; r++) {
        images[r] = QImage(150, 150, QImage::Format_ARGB32);
        images[r].fill(Qt::white);
        QPainter p(images + r);
        QPen pen(Qt::red, 0, Qt::SolidLine, Qt::FlatCap);
        p.setPen(pen);
        QVERIFY(p.pen().isCosmetic());
        if (r) {
            for (int i = 0; i < points.size()-1; i++) {
                p.drawLine(points.at(i), points.at(i+1));
            }
        } else {
            p.drawPolyline(points);
        }
    }

    QCOMPARE(images[0], images[1]);
}

void tst_QPainter::QTBUG50153_drawImage_assert()
{
    QImage::Format formats[] = {
        QImage::Format_RGB32,  // fetchTransformedBilinearARGB32PM
        QImage::Format_ARGB32  // fetchTransformedBilinear
    };

    for (unsigned i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
        QImage image(3027, 2999, formats[i]);

        QImage backingStore(image.size(), QImage::Format_ARGB32);
        QPainter backingStorePainter(&backingStore);

        QTransform transform;
        transform.scale( 0.999987, 0.999987 );

        backingStorePainter.setTransform(transform);
        backingStorePainter.setRenderHint(QPainter::SmoothPixmapTransform, true);
        backingStorePainter.drawImage(0, 0, image);

        // No crash, all fine
    }
}

void tst_QPainter::rotateImage_data()
{
    QTest::addColumn<QImage>("image");
    QTest::addColumn<bool>("smooth");

    QImage image(128, 128, QImage::Format_RGB32);
    for (int y = 0; y < 128; ++y) {
        for (int x = 0; x < 128; ++x) {
            image.setPixel(x, y, qRgb(x + y, x + y, x + y));
        }
    }

    QTest::newRow("fast") << image << false;
    QTest::newRow("smooth") << image << true;
}

void tst_QPainter::rotateImage()
{
    QFETCH(QImage, image);
    QFETCH(bool, smooth);

    QImage dest(184, 184, QImage::Format_ARGB32_Premultiplied);
    dest.fill(Qt::transparent);

    QPainter painter(&dest);
    QTransform transform;
    transform.translate(92, 0);
    transform.rotate(45);
    painter.setTransform(transform);
    painter.setRenderHint(QPainter::SmoothPixmapTransform, smooth);
    painter.drawImage(0, 0, image);
    painter.end();

    QRgb lastRow = qRgba(0, 0, 0, 0);
    for (int y = 0; y < 184; ++y) {
        QRgb row = qRgba(0, 0, 0, 0);
        for (int x = 0; x < 184; ++x) {
            QRgb pixel = dest.pixel(x, y);
            if (qAlpha(pixel) < 255)
                continue;
            if (qAlpha(row) == 0) {
                row = pixel;
            } else {
                QCOMPARE(qRed(pixel), qGreen(pixel));
                QCOMPARE(qGreen(pixel), qBlue(pixel));
                QVERIFY(qAbs(qRed(row) - qRed(pixel)) <= 2);
                QVERIFY(qAbs(qGreen(row) - qGreen(pixel)) <= 2);
                QVERIFY(qAbs(qBlue(row) - qBlue(pixel)) <= 2);
            }

        }
        if (qAlpha(row) && qAlpha(lastRow))
            QVERIFY(qGray(lastRow) <= qGray(row));
        lastRow = row;
    }

}

void tst_QPainter::QTBUG56252()
{
    QImage sourceImage(1770, 1477, QImage::Format_RGB32);
    QImage rotatedImage(1478, 1771, QImage::Format_RGB32);
    QTransform transformCenter;
    transformCenter.translate(739.0, 885.5);
    transformCenter.rotate(270.0);
    transformCenter.translate(-885.0, -738.5);
    QPainter painter;
    painter.begin(&rotatedImage);
    painter.setTransform(transformCenter);
    painter.drawImage(QPoint(0, 0),sourceImage);
    painter.end();

    // If no crash or illegal memory read, all is fine
}

void tst_QPainter::blendNullRGB32()
{
    quint32 data[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    QImage nullImage((const uchar*)data, 16, 1,  QImage::Format_RGB32);
    QImage image(16, 1, QImage::Format_RGB32);
    image.fill(Qt::white);

    QPainter paint(&image);
    paint.setCompositionMode(QPainter::CompositionMode_Source);
    paint.setOpacity(0.5);
    paint.drawImage(0, 0, nullImage);
    paint.end();

    for (int i=0; i < image.width(); ++i)
        QVERIFY(image.pixel(i,0) != 0xffffffff);
}

void tst_QPainter::toRGB64()
{
    QImage dst(10, 1, QImage::Format_BGR30);
    QImage src(10, 1, QImage::Format_RGB16);
    src.fill(Qt::white);

    QPainter paint(&dst);
    paint.drawImage(0, 0, src);
    paint.end();

    for (int i=0; i < dst.width(); ++i) {
        QVERIFY(dst.pixelColor(i,0) == QColor(Qt::white));
    }
}

void tst_QPainter::fillPolygon()
{
    QImage image(50, 50, QImage::Format_RGB32);
    image.fill(Qt::white);

    QPainter painter(&image);
    QBrush brush(Qt::black, Qt::SolidPattern);
    painter.setBrush(brush);

    QPen pen(Qt::red, 0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
    painter.setPen(pen);

    const QPoint diamondpoints[5] = {
        QPoint(-15, 0),
        QPoint(0, -15),
        QPoint(15, 0),
        QPoint(0, 15),
        QPoint(-15, 0)
    };
    enum { Outside1, Border1, Inside, Border2, Outside2 } state;

    for (int i = 0; i < 16 ; i++)
    {
        for (int j = 0; j < 16 ; j++)
        {
            image.fill(Qt::white);
            painter.resetTransform();
            painter.translate(25 + i/16., 25 + j/16.);
            painter.drawPolygon(diamondpoints, 5);

            for (int x = 0; x < 50; x++) {
                state = Outside1;
                for (int y = 0; y < 50; y++) {
                    QRgb c = image.pixel(x, y);
                    switch (state) {
                    case Outside1:
                        if (c == QColor(Qt::red).rgb())
                            state = Border1;
                        else
                            QCOMPARE(c, QColor(Qt::white).rgb());
                        break;
                    case Border1:
                        if (c == QColor(Qt::black).rgb())
                            state = Inside;
                        else if (c == QColor(Qt::white).rgb())
                            state = Outside2;
                        else
                            QCOMPARE(c, QColor(Qt::red).rgb());
                        break;
                    case Inside:
                        if (c == QColor(Qt::red).rgb())
                            state = Border2;
                        else
                            QCOMPARE(c, QColor(Qt::black).rgb());
                        break;
                    case Border2:
                        if (c == QColor(Qt::white).rgb())
                            state = Outside2;
                        else
                            QCOMPARE(c, QColor(Qt::red).rgb());
                        break;
                    case Outside2:
                        QCOMPARE(c, QColor(Qt::white).rgb());
                    }
                }
            }
            for (int y = 0; y < 50; y++) {
                state = Outside1;
                for (int x = 0; x < 50; x++) {
                    QRgb c = image.pixel(x, y);
                    switch (state) {
                    case Outside1:
                        if (c == QColor(Qt::red).rgb())
                            state = Border1;
                        else
                            QCOMPARE(c, QColor(Qt::white).rgb());
                        break;
                    case Border1:
                        if (c == QColor(Qt::black).rgb())
                            state = Inside;
                        else if (c == QColor(Qt::white).rgb())
                            state = Outside2;
                        else
                            QCOMPARE(c, QColor(Qt::red).rgb());
                        break;
                    case Inside:
                        if (c == QColor(Qt::red).rgb())
                            state = Border2;
                        else
                            QCOMPARE(c, QColor(Qt::black).rgb());
                        break;
                    case Border2:
                        if (c == QColor(Qt::white).rgb())
                            state = Outside2;
                        else
                            QCOMPARE(c, QColor(Qt::red).rgb());
                        break;
                    case Outside2:
                        QCOMPARE(c, QColor(Qt::white).rgb());
                    }
                }
            }
        }
    }
}

void tst_QPainter::textOnArgb32()
{
    QImage backing(100, 20, QImage::Format_RGB32);
    backing.fill(Qt::white);
    QImage img(100, 20, QImage::Format_ARGB32);
    img.fill(Qt::transparent); // Filled with transparent black

    QPainter imagePainter(&img);
    imagePainter.setPen(Qt::red);
    imagePainter.setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
    imagePainter.setRenderHints(QPainter::TextAntialiasing);
    imagePainter.drawText(img.rect(), Qt::AlignCenter,"Text example");
    imagePainter.end();
    imagePainter.begin(&backing);
    imagePainter.drawImage(backing.rect(), img);
    imagePainter.end();
    for (int y = 0; y < backing.height(); ++y) {
        for (int x = 0; x < backing.width(); ++x) {
            const uint32_t px = backing.pixel(x, y);
            // Red over white, should always be full red.
            QCOMPARE(qRed(px), 255);
        }
    }
}

void tst_QPainter::drawImageAtPointF()
{
    // Just test we do not crash
    QImage image1(10, 10, QImage::Format_RGB32);
    QImage image2(200, 200, QImage::Format_RGB32);

    QPainter paint(&image2);
    paint.setClipRect(97, 46, 14, 14);
    paint.setCompositionMode(QPainter::CompositionMode_Source);
    paint.drawImage(QPointF(96, std::numeric_limits<int>::max()), image1);
    paint.drawImage(QPointF(std::numeric_limits<int>::min(), 48), image1);
    paint.end();
}

void tst_QPainter::scaledDashes()
{
    // Test that we do not hit the limit-huge-number-of-dashes path
    QRgb fore = qRgb(0, 0, 0xff);
    QRgb back = qRgb(0xff, 0xff, 0);
    QImage image(5, 32, QImage::Format_RGB32);
    image.fill(back);
    QPainter p(&image);
    QPen pen(QColor(fore), 3, Qt::DotLine);
    p.setPen(pen);
    p.scale(1, 2);
    p.drawLine(2, 0, 2, 16);
    p.end();

    bool foreFound = false;
    bool backFound = false;
    int i = 0;
    while (i < 32 && (!foreFound || !backFound)) {
        QRgb pix = image.pixel(3, i);
        if (pix == fore)
            foreFound = true;
        else if (pix == back)
            backFound = true;
        i++;
    }

    QVERIFY(foreFound);
    QVERIFY(backFound);
}

#if QT_CONFIG(raster_fp)
void tst_QPainter::hdrColors()
{
    QImage img(10, 10, QImage::Format_RGBA32FPx4_Premultiplied);
    img.fill(Qt::transparent);

    QColor color = QColor::fromRgbF(2.0f, -0.25f, 1.5f);
    img.setPixelColor(2, 2, color);
    QCOMPARE(img.pixelColor(2, 2), color);

    {
        QPainterPath path;
        path.addEllipse(4, 4, 2, 2);
        QPainter p(&img);
        p.fillPath(path, color);
        p.end();
    }
    QCOMPARE(img.pixelColor(4, 4), color);

    img.fill(color);
    QCOMPARE(img.pixelColor(8, 8), color);

    QColor color2 = QColor::fromRgbF(0.0f, 1.25f, 2.5f);
    {
        QPainter p(&img);
        p.fillRect(0, 0, 3, 3, color2);
        p.end();
    }
    QCOMPARE(img.pixelColor(1, 1), color2);
    QCOMPARE(img.pixelColor(4, 4), color);

    QImage img2(10, 10, QImage::Format_RGBX32FPx4);
    img2.fill(Qt::black); // fill to avoid random FP values like Inf which can break SourceOver composition
    {
        QPainter p(&img2);
        p.drawImage(0, 0, img);
        p.end();
    }
    QCOMPARE(img2.pixelColor(2, 2), color2);
    QCOMPARE(img2.pixelColor(5, 5), color);
}
#endif

void tst_QPainter::alphaBlitToNonAlphaFormats_data()
{
    QTest::addColumn<QImage::Format>("format");

    for (int i = QImage::Format_Invalid; i < QImage::NImageFormats; ++i) {
        auto imageFormat = QImage::Format(i);
        auto pixelFormat = QImage::toPixelFormat(imageFormat);
        if (pixelFormat.colorModel() != QPixelFormat::RGB)
            continue;
        if (pixelFormat.alphaUsage() == QPixelFormat::UsesAlpha)
            continue;
        QTest::addRow("%s", QDebug::toBytes(imageFormat).mid(15).data()) << imageFormat;
    }
}

void tst_QPainter::alphaBlitToNonAlphaFormats()
{
    QFETCH(QImage::Format, format);

    {
        // Test consistent reporting of alpha 1.0 with different ways of
        // filling (or not filling) the image.

        QColor fillColors[] = {
            QColor(),
            QColor::fromRgbF(0, 0, 0, 1),
            QColor::fromRgbF(1, 1, 1, 1),
            QColor::fromRgbF(0, 0, 0, 0),
            QColor::fromRgbF(1, 1, 1, 0),
        };

        for (auto fillColor : fillColors) {
            QImage image(1, 1, format);
            if (fillColor.isValid())
                // Filling with an explicit color should always report an alpha
                // of 1.0 for non-alpha formats, regardless of the fill.
                image.fill(fillColor);
            else {
                // Test that the logic for ensuring alpha 1.0 isn't only handled
                // during the fill, by zero-initializing the underlying data. In
                // this case we should still report an alpha of 1.0.
                memset(image.bits(), 0, image.sizeInBytes());
            }

            QCOMPARE(qAlpha(image.pixel(0, 0)), 255);
            QCOMPARE(image.pixelColor(0, 0).alphaF(), 1.0);
        }
    }

    for (int i = 0; i < 2; ++i) {
        // Attempt to test both non-SIMD and SIMD paths. The latter is
        // chosen for images wider than 4-16 pixels according to Allan.
        auto size = i ? 32 : 1;
        QImage image(size, size, format);
        image.fill(Qt::black);
        QCOMPARE(image.pixelColor(0, 0).alphaF(), 1.0);

        static const auto semiTransparentColor = QColor::fromRgbF(0.2, 0.4, 0.6, 0.8);

        auto testAlpha = [&](std::function<void(QPainter*)> paintFunction) {
            QImage paintedImage = image;
            QPainter painter(&paintedImage);
            QVERIFY(painter.isActive());

            painter.setCompositionMode(QPainter::CompositionMode_Source);
            paintFunction(&painter);

            QCOMPARE(paintedImage.pixelColor(0, 0).alphaF(), 1.0);

            // Try reading the raw data, to not be affected by pixelColor/pixel()
            auto argbImage = paintedImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
            QCOMPARE(qAlpha(reinterpret_cast<const QRgb *>(argbImage.constBits())[0]), 255);
        };

        QTest::ThrowOnFailEnabler throwOnFail;

        // Test consistent reporting of alpha 1.0 after drawLine with
        // semi-transparent pen color and source composition mode.
        testAlpha([&](QPainter *painter) {
            painter->setPen(semiTransparentColor);
            painter->drawLine(QPoint(0, 0), QPoint(size, size));
        });

        // Test consistent reporting of alpha 1.0 after fillRect with
        // semi-transparent fill color and source composition mode.
        testAlpha([&](QPainter *painter) {
            painter->fillRect(QRect(0, 0, size, size), semiTransparentColor);
        });

        // Test consistent reporting of alpha 1.0 after drawImage with
        // semi-transparent image and source composition mode.
        testAlpha([&](QPainter *painter) {
            QImage alphaImage(size, size, QImage::Format_ARGB32_Premultiplied);
            alphaImage.fill(semiTransparentColor);
            painter->drawImage(QPoint(0, 0), alphaImage);
        });
    }
}

void tst_QPainter::floatRounding()
{
    // oss-fuzz issue 429123947
    // The following triggered an assert in QDashStroker::processCurrentSubpath(): "dpos >= 0"
    // when it expected the calculation's result to be zero but it was actually smaller:
    // qreal(4) + qreal(0.1) - qreal(0.1) - qreal(4)
    // actual result: -4.440892098500626e-16
    QImage img(5, 5, QImage::Format_RGB888);
    QPainter p(&img);

    QList<qreal> pattern {0.1, 0.3, 0.1, 0.1, 0.3, 0.1};
    QPainterPathStroker stroker;
    stroker.setDashPattern(pattern);

    QPainterPath pp;
    pp.moveTo(4.0, 0.0);
    pp.lineTo(0.1, 0.0);
    pp.lineTo(0.0, 0.0);
    pp.lineTo(0.0, 5.0);

    QPolygonF poly = stroker.createStroke(pp).toFillPolygon();
    p.drawPolygon(poly);
}

QTEST_MAIN(tst_QPainter)

#include "tst_qpainter.moc"
